Skip to content

Commit

Permalink
surface: add timer-based scrolling during selection
Browse files Browse the repository at this point in the history
  • Loading branch information
moni-dz committed Jan 3, 2025
1 parent b65c269 commit 7114287
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 6 deletions.
22 changes: 16 additions & 6 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3329,6 +3329,17 @@ pub fn cursorPosCallback(
// Mark the link's row as dirty, but continue with updating the
// mouse state below so we can scroll when our position is negative.
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;

// Stop selection scrolling when releasing the left mouse button
// outside the viewport.
if (self.mouse.click_state[@intFromEnum(input.MouseButton.left)] == .release and self.io_thread.scroll_active) {
self.io.queueMessage(.{ .selection_scroll = false }, .unlocked);
}
}

// Stop selection scrolling when inside the viewport.
if (pos.x >= 0 and pos.y >= 0 and self.io_thread.scroll_active) {
self.io.queueMessage(.{ .selection_scroll = false }, .unlocked);
}

// Always show the mouse again if it is hidden
Expand Down Expand Up @@ -3432,13 +3443,12 @@ pub fn cursorPosCallback(
// Note: one day, we can change this from distance to time based if we want.
//log.warn("CURSOR POS: {} {}", .{ pos, self.size.screen });
const max_y: f32 = @floatFromInt(self.size.screen.height);
if (pos.y <= 1 or pos.y > max_y - 1) {
const delta: isize = if (pos.y < 0) -1 else 1;
try self.io.terminal.scrollViewport(.{ .delta = delta });

// TODO: We want a timer or something to repeat while we're still
// at this cursor position. Right now, the user has to jiggle their
// mouse in order to scroll.
// Only send a message when outside the viewport and
// selection scrolling is not currently active.
if ((pos.y <= 1 or pos.y > max_y - 1) and !self.io_thread.scroll_active) {
// TODO: the selection region ideally should keep up with this
self.io.queueMessage(.{ .selection_scroll = true }, .locked);
}

// Convert to points
Expand Down
64 changes: 64 additions & 0 deletions src/termio/Thread.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const Coalesce = struct {
/// The number of milliseconds before we reset the synchronized output flag
/// if the running program hasn't already.
const sync_reset_ms = 1000;
const selection_scroll_ms = 15;

/// Allocator used for some state
alloc: std.mem.Allocator,
Expand All @@ -52,6 +53,11 @@ wakeup_c: xev.Completion = .{},
stop: xev.Async,
stop_c: xev.Completion = .{},

/// The timer used for selection scrolling
scroll: xev.Timer,
scroll_c: xev.Completion = .{},
scroll_active: bool = false,

/// This is used to coalesce resize events.
coalesce: xev.Timer,
coalesce_c: xev.Completion = .{},
Expand Down Expand Up @@ -91,6 +97,10 @@ pub fn init(
var stop_h = try xev.Async.init();
errdefer stop_h.deinit();

// This timer is used for selection scrolling.
var scroll_h = try xev.Timer.init();
errdefer scroll_h.deinit();

// This timer is used to coalesce resize events.
var coalesce_h = try xev.Timer.init();
errdefer coalesce_h.deinit();
Expand All @@ -103,6 +113,7 @@ pub fn init(
.alloc = alloc,
.loop = loop,
.stop = stop_h,
.scroll = scroll_h,
.coalesce = coalesce_h,
.sync_reset = sync_reset_h,
};
Expand All @@ -111,6 +122,7 @@ pub fn init(
/// Clean up the thread. This is only safe to call once the thread
/// completes executing; the caller must join prior to this.
pub fn deinit(self: *Thread) void {
self.scroll.deinit();
self.coalesce.deinit();
self.sync_reset.deinit();
self.stop.deinit();
Expand Down Expand Up @@ -280,6 +292,13 @@ fn drainMailbox(
.size_report => |v| try io.sizeReport(data, v),
.clear_screen => |v| try io.clearScreen(data, v.history),
.scroll_viewport => |v| try io.scrollViewport(v),
.selection_scroll => |v| {
if (v) {
self.startScrollTimer(cb);
} else {
self.stopScrollTimer();
}
},
.jump_to_prompt => |v| try io.jumpToPrompt(v),
.start_synchronized_output => self.startSynchronizedOutput(cb),
.linefeed_mode => |v| self.flags.linefeed_mode = v,
Expand Down Expand Up @@ -419,3 +438,48 @@ fn stopCallback(
cb_.?.self.loop.stop();
return .disarm;
}

fn startScrollTimer(self: *Thread, cb: *CallbackData) void {
self.scroll_active = true;

// Start the timer which loops
self.scroll.run(&self.loop, &self.scroll_c, selection_scroll_ms, CallbackData, cb, selectionScrollCallback);
}

fn stopScrollTimer(self: *Thread) void {
// This will stop the scrolling on the next iteration.
self.scroll_active = false;
}

fn selectionScrollCallback(
cb_: ?*CallbackData,
_: *xev.Loop,
_: *xev.Completion,
r: xev.Timer.RunError!void,
) xev.CallbackAction {
_ = r catch |err| switch (err) {
error.Canceled => {},
else => {
log.warn("error during selection scroll callback err={}", .{err});
return .disarm;
},
};

const cb = cb_ orelse return .disarm;

const pos = try cb.io.surface_mailbox.surface.rt_surface.getCursorPos();
const delta: isize = if (pos.y < 0) -1 else 1;

try cb.io.terminal.scrollViewport(.{ .delta = delta });

// Notify the renderer that it should repaint immediately after scrolling
cb.io.renderer_wakeup.notify() catch {};

const self = cb.self;

if (self.scroll_active) {
self.scroll.run(&self.loop, &self.scroll_c, selection_scroll_ms, CallbackData, cb, selectionScrollCallback);
}

return .disarm;
}
3 changes: 3 additions & 0 deletions src/termio/message.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ pub const Message = union(enum) {
/// Scroll the viewport
scroll_viewport: terminal.Terminal.ScrollViewport,

/// Selection scrolling
selection_scroll: bool,

/// Jump forward/backward n prompts.
jump_to_prompt: isize,

Expand Down

0 comments on commit 7114287

Please sign in to comment.