Skip to content

Commit 2018a8f

Browse files
committed
scroll: translate non-precision to precision
Some wheel mice are capable of reporting fractional wheel ticks. These mice don't necessarily report a corresponding precision scroll start event, at least in Wayland + GTK. We can treat all discrete (ie non-precision) events as the number of wheel ticks - for wheel mice, yoff will be "1.0" per tick, while precision wheel mice may report fractional values. This unifies handling of scroll events by normalizing all events to "pixels to scroll". We now report `mouse-scroll-multiplier` wheel or arrow events per wheel tick (or per accumulated cell height). This means that applications which subscribe to mouse button events will receive (by default) three wheel events per wheel tick. For precision scrolls, they will receive one wheel tick per line of scroll. In my opinion, this provides the best user experience while also allowing customization of how much a wheel tick should scroll Reference: ghostty-org#6677
1 parent 644acda commit 2018a8f

File tree

1 file changed

+13
-33
lines changed

1 file changed

+13
-33
lines changed

src/Surface.zig

+13-33
Original file line numberDiff line numberDiff line change
@@ -2325,13 +2325,6 @@ const ScrollAmount = struct {
23252325
pub fn magnitude(self: ScrollAmount) usize {
23262326
return @abs(self.delta);
23272327
}
2328-
2329-
pub fn multiplied(self: ScrollAmount, multiplier: f64) ScrollAmount {
2330-
const delta_f64: f64 = @floatFromInt(self.delta);
2331-
const delta_adjusted: f64 = delta_f64 * multiplier;
2332-
const delta_isize: isize = @intFromFloat(@round(delta_adjusted));
2333-
return .{ .delta = delta_isize };
2334-
}
23352328
};
23362329

23372330
/// Mouse scroll event. Negative is down, left. Positive is up, right.
@@ -2355,20 +2348,18 @@ pub fn scrollCallback(
23552348
if (self.mouse.hidden) self.showMouse();
23562349

23572350
const y: ScrollAmount = if (yoff == 0) .{} else y: {
2358-
// Non-precision scrolls don't accumulate. We cast that raw yoff to an isize and interpret
2359-
// it as the number of lines to scroll.
2360-
if (!scroll_mods.precision) {
2361-
// Calculate our magnitude of scroll. This is a direct multiple of yoff
2362-
const y_delta_isize: isize = @intFromFloat(@round(yoff));
2363-
break :y .{ .delta = y_delta_isize };
2364-
}
2365-
2366-
// Precision scrolling is more complicated. We need to maintain state
2367-
// to build up a pending scroll amount if we're only scrolling by a
2368-
// tiny amount so that we can scroll by a full row when we have enough.
2351+
// We use cell_size to determine if we have accumulated enough to trigger a scroll
2352+
const cell_size: f64 = @floatFromInt(self.size.cell.height);
23692353

2370-
// Adjust our offset by the multiplier
2371-
const yoff_adjusted: f64 = yoff;
2354+
// If we have precision scroll, yoff is the number of pixels to scroll. In non-precision
2355+
// scroll, yoff is the number of wheel ticks. Some mice are capable of reporting fractional
2356+
// wheel ticks, which don't necessarily get reported as precision scrolls. We normalize all
2357+
// scroll events to pixels by multiplying the wheel tick value and the cell size. This means
2358+
// that a wheel tick of 1 results in single scroll event.
2359+
const yoff_adjusted: f64 = if (scroll_mods.precision)
2360+
yoff
2361+
else
2362+
yoff * cell_size * self.config.mouse_scroll_multiplier;
23722363

23732364
// Add our previously saved pending amount to the offset to get the
23742365
// new offset value. The signs of the pending and yoff should match
@@ -2379,7 +2370,6 @@ pub fn scrollCallback(
23792370

23802371
// If the new offset is less than a single unit of scroll, we save
23812372
// the new pending value and do not scroll yet.
2382-
const cell_size: f64 = @floatFromInt(self.size.cell.height);
23832373
if (@abs(poff) < cell_size) {
23842374
self.mouse.pending_scroll_y = poff;
23852375
break :y .{};
@@ -2432,12 +2422,6 @@ pub fn scrollCallback(
24322422
try self.setSelection(null);
24332423
}
24342424

2435-
// We never use a multiplier for precision scrolls.
2436-
const multiplier: f64 = if (scroll_mods.precision)
2437-
1.0
2438-
else
2439-
self.config.mouse_scroll_multiplier;
2440-
24412425
// If we're in alternate screen with alternate scroll enabled, then
24422426
// we convert to cursor keys. This only happens if we're:
24432427
// (1) alt screen (2) no explicit mouse reporting and (3) alt
@@ -2464,9 +2448,7 @@ pub fn scrollCallback(
24642448
.down_left => "\x1b[B",
24652449
};
24662450
};
2467-
// We multiple by the scroll multiplier when reporting arrows
2468-
const multiplied = y.multiplied(multiplier);
2469-
for (0..multiplied.magnitude()) |_| {
2451+
for (0..y.magnitude()) |_| {
24702452
self.io.queueMessage(.{ .write_stable = seq }, .locked);
24712453
}
24722454
}
@@ -2502,12 +2484,10 @@ pub fn scrollCallback(
25022484
}
25032485

25042486
if (y.delta != 0) {
2505-
// We multiply by the multiplier when scrolling the viewport
2506-
const multiplied = y.multiplied(multiplier);
25072487
// Modify our viewport, this requires a lock since it affects
25082488
// rendering. We have to switch signs here because our delta
25092489
// is negative down but our viewport is positive down.
2510-
try self.io.terminal.scrollViewport(.{ .delta = multiplied.delta * -1 });
2490+
try self.io.terminal.scrollViewport(.{ .delta = y.delta * -1 });
25112491
}
25122492
}
25132493

0 commit comments

Comments
 (0)