various cleanups around scroll timers

pull/4422/head
Mitchell Hashimoto 2025-06-30 09:18:27 -07:00
parent f73c90bf5d
commit 81cef6e63b
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
4 changed files with 97 additions and 52 deletions

View File

@ -949,36 +949,50 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
};
},
.selection_scroll => |active| {
.selection_scroll_tick => |active| {
self.selection_scroll_active = active;
if (self.selection_scroll_active) {
const pos = try self.rt_surface.getCursorPos();
const pos_vp = self.posToViewport(pos.x, pos.y);
const screen = &self.renderer_state.terminal.screen;
const delta: isize = if (pos.y < 0) -1 else 1;
try self.io.terminal.scrollViewport(.{ .delta = delta });
// Always the case, but doesn't hurt to check
if (self.mouse.left_click_count == 1) {
const pin = screen.pages.pin(.{
.viewport = .{
.x = pos_vp.x,
.y = pos_vp.y,
},
}) orelse {
if (comptime std.debug.runtime_safety) unreachable;
return;
};
try self.dragLeftClickSingle(pin, pos.x);
}
}
try self.selectionScrollTick();
},
}
}
fn selectionScrollTick(self: *Surface) !void {
// If we're no longer active then we don't do anything.
if (!self.selection_scroll_active) return;
// If we don't have a left mouse button down then we
// don't do anything.
if (self.mouse.left_click_count == 0) return;
const pos = try self.rt_surface.getCursorPos();
const pos_vp = self.posToViewport(pos.x, pos.y);
const delta: isize = if (pos.y < 0) -1 else 1;
// We need our locked state for the remainder
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
const t: *terminal.Terminal = self.renderer_state.terminal;
// Scroll the viewport as required
try t.scrollViewport(.{ .delta = delta });
// Next, trigger our drag behavior
const pin = t.screen.pages.pin(.{
.viewport = .{
.x = pos_vp.x,
.y = pos_vp.y,
},
}) orelse {
if (comptime std.debug.runtime_safety) unreachable;
return;
};
try self.dragLeftClickSingle(pin, pos.x);
// We modified our viewport and selection so we need to queue
// a render.
try self.queueRender();
}
fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
// Mark our flag that we exited immediately
self.child_exited = true;
@ -3264,6 +3278,15 @@ pub fn mouseButtonCallback(
}
if (button == .left and action == .release) {
// Stop selection scrolling when releasing the left mouse button
// but only when selection scrolling is active.
if (self.selection_scroll_active) {
self.io.queueMessage(
.{ .selection_scroll = false },
.unlocked,
);
}
// The selection clipboard is only updated for left-click drag when
// the left button is released. This is to avoid the clipboard
// being updated on every mouse move which would be noisy.
@ -3291,12 +3314,6 @@ pub fn mouseButtonCallback(
log.warn("error processing links err={}", .{err});
}
}
// Stop selection scrolling when releasing the left mouse button
// but only when selection scrolling is active.
if (self.selection_scroll_active) {
self.io.queueMessage(.{ .selection_scroll = false }, .unlocked);
}
}
// Report mouse events if enabled
@ -3804,12 +3821,6 @@ pub fn cursorPosCallback(
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
}
// Stop selection scrolling when inside the viewport within a 1px buffer
// for fullscreen windows, but only when selection scrolling is active.
if (pos.x >= 1 and pos.y >= 1 and self.selection_scroll_active) {
self.io.queueMessage(.{ .selection_scroll = false }, .unlocked);
}
// Always show the mouse again if it is hidden
if (self.mouse.hidden) self.showMouse();
@ -3829,6 +3840,15 @@ pub fn cursorPosCallback(
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
// Stop selection scrolling when inside the viewport within a 1px buffer
// for fullscreen windows, but only when selection scrolling is active.
if (pos.x >= 1 and pos.y >= 1 and self.selection_scroll_active) {
self.io.queueMessage(
.{ .selection_scroll = false },
.locked,
);
}
// Update our mouse state. We set this to null initially because we only
// want to set it when we're not selecting or doing any other mouse
// event.
@ -3912,10 +3932,15 @@ pub fn cursorPosCallback(
//log.warn("CURSOR POS: {} {}", .{ pos, self.size.screen });
const max_y: f32 = @floatFromInt(self.size.screen.height);
// 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.selection_scroll_active) {
self.io.queueMessage(.{ .selection_scroll = true }, .locked);
// If the mouse is outside the viewport and we have the left
// mouse button pressed then we need to start the scroll timer.
if ((pos.y <= 1 or pos.y > max_y - 1) and
!self.selection_scroll_active)
{
self.io.queueMessage(
.{ .selection_scroll = true },
.locked,
);
}
// Convert to points

View File

@ -79,8 +79,12 @@ pub const Message = union(enum) {
color: terminal.color.RGB,
},
// Tell the surface to perform selection scrolling.
selection_scroll: bool,
/// Notifies the surface that a tick of the timer that is timing
/// out selection scrolling has occurred. "selection scrolling"
/// is when the user has clicked and dragged the mouse outside
/// the viewport of the terminal and the terminal is scrolling
/// the viewport to follow the mouse cursor.
selection_scroll_tick: bool,
/// The terminal has reported a change in the working directory.
pwd_change: WriteReq,

View File

@ -472,7 +472,14 @@ 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);
self.scroll.run(
&self.loop,
&self.scroll_c,
selection_scroll_ms,
CallbackData,
cb,
selectionScrollCallback,
);
}
fn stopScrollTimer(self: *Thread) void {
@ -497,14 +504,20 @@ fn selectionScrollCallback(
const cb = cb_ orelse return .disarm;
const self = cb.self;
_ = cb.io.surface_mailbox.push(.{ .selection_scroll = self.scroll_active }, .{ .instant = {} });
// Send the tick to the main surface
_ = cb.io.surface_mailbox.push(
.{ .selection_scroll_tick = self.scroll_active },
.{ .instant = {} },
);
// Notify the renderer that it should repaint immediately after scrolling
cb.io.renderer_wakeup.notify() catch {};
if (self.scroll_active) {
self.scroll.run(&self.loop, &self.scroll_c, selection_scroll_ms, CallbackData, cb, selectionScrollCallback);
}
if (self.scroll_active) self.scroll.run(
&self.loop,
&self.scroll_c,
selection_scroll_ms,
CallbackData,
cb,
selectionScrollCallback,
);
return .disarm;
}

View File

@ -48,7 +48,10 @@ pub const Message = union(enum) {
/// Scroll the viewport
scroll_viewport: terminal.Terminal.ScrollViewport,
/// Selection scrolling
/// Selection scrolling. If this is set to true then the termio
/// thread starts a timer that will trigger a `selection_scroll_tick`
/// message back to the surface. This ping/pong is because the
/// surface thread doesn't have access to an event loop from libghostty.
selection_scroll: bool,
/// Jump forward/backward n prompts.