core: do high precision scrolling Y calculations

This commit is contained in:
Mitchell Hashimoto
2023-06-29 11:42:43 -07:00
parent 9a40dc4630
commit 32031701b8
2 changed files with 58 additions and 13 deletions

View File

@ -298,8 +298,8 @@ extension Ghostty {
var y = event.scrollingDeltaY var y = event.scrollingDeltaY
if event.hasPreciseScrollingDeltas { if event.hasPreciseScrollingDeltas {
mods = 1 mods = 1
x *= 0.1
y *= 0.1 // TODO(mitchellh): do we have to scale the x/y here?
} }
// Determine our momentum value // Determine our momentum value

View File

@ -127,6 +127,10 @@ const Mouse = struct {
/// The last x/y sent for mouse reports. /// The last x/y sent for mouse reports.
event_point: terminal.point.Viewport = .{}, event_point: terminal.point.Viewport = .{},
/// Pending scroll amounts for high-precision scrolls
pending_scroll_x: f64 = 0,
pending_scroll_y: f64 = 0,
}; };
/// The configuration that a surface has, this is copied from the main /// The configuration that a surface has, this is copied from the main
@ -1274,8 +1278,6 @@ pub fn scrollCallback(
yoff: f64, yoff: f64,
scroll_mods: input.ScrollMods, scroll_mods: input.ScrollMods,
) !void { ) !void {
_ = scroll_mods;
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -1292,16 +1294,59 @@ pub fn scrollCallback(
// log.info("SCROLL: xoff={} yoff={} mods={}", .{ xoff, yoff, scroll_mods }); // log.info("SCROLL: xoff={} yoff={} mods={}", .{ xoff, yoff, scroll_mods });
// Positive is up const ScrollAmount = struct {
// Positive is up, right
sign: isize = 1,
delta_unsigned: usize = 0,
delta: isize = 0,
};
const y: ScrollAmount = if (yoff == 0) .{} else y: {
// Non-precision scrolling is easy to calculate.
if (!scroll_mods.precision) {
const y_sign: isize = if (yoff > 0) -1 else 1; const y_sign: isize = if (yoff > 0) -1 else 1;
const y_delta_unsigned: usize = if (yoff == 0) 0 else @max(@divFloor(self.grid_size.rows, 15), 1); const y_delta_unsigned: usize = @max(@divFloor(self.grid_size.rows, 15), 1);
const y_delta: isize = y_sign * @intCast(isize, y_delta_unsigned); const y_delta: isize = y_sign * @intCast(isize, y_delta_unsigned);
break :y .{ .sign = y_sign, .delta_unsigned = y_delta_unsigned, .delta = y_delta };
}
// Precision scrolling is more complicated. We need to maintain state
// to build up a pending scroll amount if we're only scrolling by a
// tiny amount so that we can scroll by a full row when we have enough.
// Add our previously saved pending amount to the offset to get the
// new offset value.
//
// NOTE: we currently mutiply by -1 because macOS sends the opposite
// of what we expect. This is jank we should audit our sign usage and
// carefully document what we expect so this can work cross platform.
// Right now this isn't important because macOS is the only high-precision
// scroller.
const poff = self.mouse.pending_scroll_y + (yoff * -1);
// If the new offset is less than a single unit of scroll, we save
// the new pending value and do not scroll yet.
const cell_size = self.cell_size.height;
if (@fabs(poff) < cell_size) {
self.mouse.pending_scroll_y = poff;
break :y .{};
}
// We scroll by the number of rows in the offset and save the remainder
const amount = poff / cell_size;
self.mouse.pending_scroll_y = poff - (amount * cell_size);
break :y .{
.delta_unsigned = @intFromFloat(usize, @fabs(amount)),
.delta = @intFromFloat(isize, amount),
};
};
// Positive is right // Positive is right
const x_sign: isize = if (xoff < 0) -1 else 1; const x_sign: isize = if (xoff < 0) -1 else 1;
const x_delta_unsigned: usize = if (xoff == 0) 0 else 1; const x_delta_unsigned: usize = if (xoff == 0) 0 else 1;
const x_delta: isize = x_sign * @intCast(isize, x_delta_unsigned); const x_delta: isize = x_sign * @intCast(isize, x_delta_unsigned);
log.info("scroll: delta_y={} delta_x={}", .{ y_delta, x_delta }); log.info("scroll: delta_y={} delta_x={}", .{ y.delta, x_delta });
{ {
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
@ -1322,9 +1367,9 @@ pub fn scrollCallback(
self.io.terminal.modes.mouse_event == .none and self.io.terminal.modes.mouse_event == .none and
self.io.terminal.modes.mouse_alternate_scroll) self.io.terminal.modes.mouse_alternate_scroll)
{ {
if (y_delta_unsigned > 0) { if (y.delta_unsigned > 0) {
const seq = if (y_delta < 0) "\x1bOA" else "\x1bOB"; const seq = if (y.delta < 0) "\x1bOA" else "\x1bOB";
for (0..y_delta_unsigned) |_| { for (0..y.delta_unsigned) |_| {
_ = self.io_thread.mailbox.push(.{ _ = self.io_thread.mailbox.push(.{
.write_stable = seq, .write_stable = seq,
}, .{ .forever = {} }); }, .{ .forever = {} });
@ -1350,7 +1395,7 @@ pub fn scrollCallback(
// the normal logic. // the normal logic.
// Modify our viewport, this requires a lock since it affects rendering // Modify our viewport, this requires a lock since it affects rendering
try self.io.terminal.scrollViewport(.{ .delta = y_delta }); try self.io.terminal.scrollViewport(.{ .delta = y.delta });
// If we're scrolling up or down, then send a mouse event. This requires // If we're scrolling up or down, then send a mouse event. This requires
// a lock since we read terminal state. // a lock since we read terminal state.