mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-28 15:55:20 +00:00
terminal: SelectionGesture autoscrollTick
This commit is contained in:
@@ -1171,15 +1171,15 @@ 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.selection_gesture.left_click_count == 0) return;
|
||||
|
||||
const delta: isize = switch (self.mouse.selection_gesture.left_drag_autoscroll) {
|
||||
.none => return,
|
||||
.up => -1,
|
||||
.down => 1,
|
||||
};
|
||||
// If our gesture doesn't want autoscrolling then disable it.
|
||||
const was_autoscrolling = self.mouse.selection_gesture.left_drag_autoscroll != .none;
|
||||
if (!was_autoscrolling) {
|
||||
self.queueIo(
|
||||
.{ .selection_scroll = false },
|
||||
.unlocked,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = try self.rt_surface.getCursorPos();
|
||||
const pos_vp = self.posToViewport(pos.x, pos.y);
|
||||
@@ -1189,20 +1189,6 @@ fn selectionScrollTick(self: *Surface) !void {
|
||||
defer self.renderer_state.mutex.unlock();
|
||||
const t: *terminal.Terminal = self.renderer_state.terminal;
|
||||
|
||||
// If our left-click pin no longer belongs to the active screen, we stop
|
||||
// our selection scroll.
|
||||
if (self.mouse.activeLeftClickPin(&t.screens) == null) {
|
||||
self.queueIo(
|
||||
.{ .selection_scroll = false },
|
||||
.locked,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll the viewport as required
|
||||
t.scrollViewport(.{ .delta = delta });
|
||||
|
||||
// Next, trigger our drag behavior
|
||||
const pin = t.screens.active.pages.pin(.{
|
||||
.viewport = .{
|
||||
.x = pos_vp.x,
|
||||
@@ -1212,7 +1198,8 @@ fn selectionScrollTick(self: *Surface) !void {
|
||||
if (comptime std.debug.runtime_safety) unreachable;
|
||||
return;
|
||||
};
|
||||
if (self.mouse.selection_gesture.drag(t, .{
|
||||
|
||||
const selection = self.mouse.selection_gesture.autoscrollTick(t, .{
|
||||
.pin = pin,
|
||||
.xpos = pos.x,
|
||||
.ypos = pos.y,
|
||||
@@ -1224,14 +1211,23 @@ fn selectionScrollTick(self: *Surface) !void {
|
||||
.padding_left = self.size.padding.left,
|
||||
.screen_height = self.size.screen.height,
|
||||
},
|
||||
})) |sel| {
|
||||
try self.io.terminal.screens.active.select(sel);
|
||||
} else {
|
||||
try self.io.terminal.screens.active.select(null);
|
||||
});
|
||||
|
||||
// If we're no longer autoscrolling for whatever reason, disable it.
|
||||
if (self.mouse.selection_gesture.left_drag_autoscroll == .none) {
|
||||
self.queueIo(
|
||||
.{ .selection_scroll = false },
|
||||
.locked,
|
||||
);
|
||||
}
|
||||
|
||||
// If our left click was invalidated, ignore the result. This isn't
|
||||
// strictly necessary but its a nice to have.
|
||||
if (self.mouse.selection_gesture.left_click_count == 0) return;
|
||||
|
||||
// We modified our viewport and selection so we need to queue
|
||||
// a render.
|
||||
try self.io.terminal.screens.active.select(selection);
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,8 @@ left_drag_autoscroll: Autoscroll,
|
||||
/// surface bounds and reset whenever there is no active drag gesture.
|
||||
///
|
||||
/// When autoscroll is non-none, the caller should setup a timer
|
||||
/// to periodically scroll the screen the desired direction a certain
|
||||
/// amount. The timer and amount is up to the caller but reasonable
|
||||
/// defaults are approximately one row every 15 milliseconds.
|
||||
/// to periodically call autoscrollTick. The timer interval is up to the
|
||||
/// caller but reasonable defaults are approximately every 15 milliseconds.
|
||||
///
|
||||
/// This is used to implement selection above/below the viewport that
|
||||
/// wants to drag the viewport.
|
||||
@@ -241,6 +240,37 @@ pub fn drag(
|
||||
};
|
||||
}
|
||||
|
||||
/// Record a selection autoscroll tick for the active left-click drag gesture.
|
||||
/// This scrolls the viewport in the active autoscroll direction and then
|
||||
/// continues the drag at the provided position.
|
||||
pub fn autoscrollTick(
|
||||
self: *SelectionGesture,
|
||||
t: *Terminal,
|
||||
d: Drag,
|
||||
) ?Selection {
|
||||
if (self.left_click_count == 0) {
|
||||
assert(self.left_drag_autoscroll == .none);
|
||||
return null;
|
||||
}
|
||||
|
||||
const delta: isize = switch (self.left_drag_autoscroll) {
|
||||
.none => return null,
|
||||
.up => -1,
|
||||
.down => 1,
|
||||
};
|
||||
|
||||
// If our click pin no longer belongs to the active screen, the gesture is
|
||||
// no longer valid. Stop it so callers can stop their autoscroll timer
|
||||
// without clearing the current selection as if this were a real drag.
|
||||
_ = self.validatedLeftClickPin(&t.screens) orelse {
|
||||
self.reset(t);
|
||||
return null;
|
||||
};
|
||||
|
||||
t.scrollViewport(.{ .delta = delta });
|
||||
return self.drag(t, d);
|
||||
}
|
||||
|
||||
pub const Release = struct {
|
||||
/// The cell where the release occurred, if the release position mapped to
|
||||
/// a valid cell. This is used synchronously to update gesture state and is
|
||||
@@ -1114,6 +1144,55 @@ test "SelectionGesture drag autoscroll edge boundaries" {
|
||||
try testing.expectEqual(.down, gesture.left_drag_autoscroll);
|
||||
}
|
||||
|
||||
test "SelectionGesture autoscroll tick scrolls and continues drag" {
|
||||
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
var gesture: SelectionGesture = .init;
|
||||
defer gesture.deinit(&t);
|
||||
|
||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
||||
press_event.xpos = 10;
|
||||
try gesture.press(&t, press_event);
|
||||
|
||||
_ = gesture.drag(&t, testDrag(&t, 3, 1, 39, 100));
|
||||
try testing.expectEqual(.down, gesture.left_drag_autoscroll);
|
||||
|
||||
const sel = gesture.autoscrollTick(&t, testDrag(&t, 3, 2, 39, 100)).?;
|
||||
try testing.expectEqual(.down, gesture.left_drag_autoscroll);
|
||||
try testing.expectEqual(true, gesture.left_click_dragged);
|
||||
try testing.expectEqualDeep(Selection.init(
|
||||
testPin(&t, 1, 1),
|
||||
testPin(&t, 3, 2),
|
||||
false,
|
||||
), sel);
|
||||
}
|
||||
|
||||
test "SelectionGesture autoscroll tick stops with invalidated click" {
|
||||
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
var gesture: SelectionGesture = .init;
|
||||
defer gesture.deinit(&t);
|
||||
|
||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
||||
press_event.xpos = 10;
|
||||
try gesture.press(&t, press_event);
|
||||
|
||||
_ = gesture.drag(&t, testDrag(&t, 2, 1, 20, 1));
|
||||
try testing.expectEqual(.up, gesture.left_drag_autoscroll);
|
||||
|
||||
_ = try t.screens.getInit(testing.allocator, .alternate, .{
|
||||
.cols = t.cols,
|
||||
.rows = t.rows,
|
||||
});
|
||||
t.screens.switchTo(.alternate);
|
||||
|
||||
try testing.expectEqual(null, gesture.autoscrollTick(&t, testDrag(&t, 2, 1, 20, 1)));
|
||||
try testing.expectEqual(.none, gesture.left_drag_autoscroll);
|
||||
try testing.expectEqual(@as(u3, 0), gesture.left_click_count);
|
||||
}
|
||||
|
||||
test "SelectionGesture drag with invalidated click returns null" {
|
||||
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
Reference in New Issue
Block a user