diff --git a/src/Surface.zig b/src/Surface.zig index 6651dd8c2..f75017053 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2325,6 +2325,13 @@ const ScrollAmount = struct { pub fn magnitude(self: ScrollAmount) usize { return @abs(self.delta); } + + pub fn multiplied(self: ScrollAmount, multiplier: f64) ScrollAmount { + const delta_f64: f64 = @floatFromInt(self.delta); + const delta_adjusted: f64 = delta_f64 * multiplier; + const delta_isize: isize = @intFromFloat(@round(delta_adjusted)); + return .{ .delta = delta_isize }; + } }; /// Mouse scroll event. Negative is down, left. Positive is up, right. @@ -2348,22 +2355,11 @@ pub fn scrollCallback( if (self.mouse.hidden) self.showMouse(); const y: ScrollAmount = if (yoff == 0) .{} else y: { - // Non-precision scrolling is easy to calculate. We don't use - // the given offset at all and instead just treat a positive - // as a scroll up and a negative as a scroll down and scroll in - // steps. + // Non-precision scrolls don't accumulate. We cast that raw yoff to an isize and interpret + // it as the number of lines to scroll. if (!scroll_mods.precision) { - // Calculate our magnitude of scroll. This is constant (not - // dependent on yoff). - const grid_size = self.size.grid(); - const grid_rows_f64: f64 = @floatFromInt(grid_size.rows); - const y_delta_f64: f64 = @round((grid_rows_f64 * self.config.mouse_scroll_multiplier) / 15.0); - const y_delta_usize: usize = @max(1, @as(usize, @intFromFloat(y_delta_f64))); - - // Calculate our direction of scroll based on the sign of yoff. - const y_sign: isize = if (yoff >= 0) 1 else -1; - const y_delta_isize: isize = y_sign * @as(isize, @intCast(y_delta_usize)); - + // Calculate our magnitude of scroll. This is a direct multiple of yoff + const y_delta_isize: isize = @intFromFloat(@round(yoff)); break :y .{ .delta = y_delta_isize }; } @@ -2372,7 +2368,7 @@ pub fn scrollCallback( // tiny amount so that we can scroll by a full row when we have enough. // Adjust our offset by the multiplier - const yoff_adjusted: f64 = yoff * self.config.mouse_scroll_multiplier; + const yoff_adjusted: f64 = yoff; // Add our previously saved pending amount to the offset to get the // new offset value. The signs of the pending and yoff should match @@ -2404,15 +2400,11 @@ pub fn scrollCallback( // For detailed comments see the y calculation above. const x: ScrollAmount = if (xoff == 0) .{} else x: { if (!scroll_mods.precision) { - const x_delta_f64: f64 = @round(1 * self.config.mouse_scroll_multiplier); - const x_delta_usize: usize = @max(1, @as(usize, @intFromFloat(x_delta_f64))); - const x_sign: isize = if (xoff >= 0) 1 else -1; - const x_delta_isize: isize = x_sign * @as(isize, @intCast(x_delta_usize)); + const x_delta_isize: isize = @intFromFloat(@round(xoff)); break :x .{ .delta = x_delta_isize }; } - const xoff_adjusted: f64 = xoff * self.config.mouse_scroll_multiplier; - const poff: f64 = self.mouse.pending_scroll_x + xoff_adjusted; + const poff: f64 = self.mouse.pending_scroll_x + xoff; const cell_size: f64 = @floatFromInt(self.size.cell.width); if (@abs(poff) < cell_size) { self.mouse.pending_scroll_x = poff; @@ -2440,6 +2432,12 @@ pub fn scrollCallback( try self.setSelection(null); } + // We never use a multiplier for precision scrolls. + const multiplier: f64 = if (scroll_mods.precision) + 1.0 + else + self.config.mouse_scroll_multiplier; + // If we're in alternate screen with alternate scroll enabled, then // we convert to cursor keys. This only happens if we're: // (1) alt screen (2) no explicit mouse reporting and (3) alt @@ -2466,7 +2464,9 @@ pub fn scrollCallback( .down_left => "\x1b[B", }; }; - for (0..y.magnitude()) |_| { + // We multiple by the scroll multiplier when reporting arrows + const multiplied = y.multiplied(multiplier); + for (0..multiplied.magnitude()) |_| { self.io.queueMessage(.{ .write_stable = seq }, .locked); } } @@ -2502,10 +2502,12 @@ pub fn scrollCallback( } if (y.delta != 0) { + // We multiply by the multiplier when scrolling the viewport + const multiplied = y.multiplied(multiplier); // Modify our viewport, this requires a lock since it affects // rendering. We have to switch signs here because our delta // is negative down but our viewport is positive down. - try self.io.terminal.scrollViewport(.{ .delta = y.delta * -1 }); + try self.io.terminal.scrollViewport(.{ .delta = multiplied.delta * -1 }); } } diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 7d471ff75..ba2e4d244 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -304,6 +304,9 @@ cgroup_path: ?[]const u8 = null, /// Our context menu. context_menu: Menu(Surface, "context_menu", false), +/// True when we have a precision scroll in progress +precision_scroll: bool = false, + /// The state of the key event while we're doing IM composition. /// See gtkKeyPressed for detailed descriptions. pub const IMKeyEvent = enum { @@ -418,10 +421,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void { c.gtk_widget_add_controller(@ptrCast(@alignCast(overlay)), ec_motion); // Scroll events - const ec_scroll = c.gtk_event_controller_scroll_new( - c.GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES | - c.GTK_EVENT_CONTROLLER_SCROLL_DISCRETE, - ); + const ec_scroll = c.gtk_event_controller_scroll_new(c.GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); errdefer c.g_object_unref(ec_scroll); c.gtk_widget_add_controller(@ptrCast(overlay), ec_scroll); @@ -532,6 +532,8 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void { _ = c.g_signal_connect_data(ec_motion, "motion", c.G_CALLBACK(>kMouseMotion), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(ec_motion, "leave", c.G_CALLBACK(>kMouseLeave), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(ec_scroll, "scroll", c.G_CALLBACK(>kMouseScroll), self, null, c.G_CONNECT_DEFAULT); + _ = c.g_signal_connect_data(ec_scroll, "scroll-begin", c.G_CALLBACK(>kMouseScrollPrecisionBegin), self, null, c.G_CONNECT_DEFAULT); + _ = c.g_signal_connect_data(ec_scroll, "scroll-end", c.G_CALLBACK(>kMouseScrollPrecisionEnd), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(im_context, "preedit-start", c.G_CALLBACK(>kInputPreeditStart), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(im_context, "preedit-changed", c.G_CALLBACK(>kInputPreeditChanged), self, null, c.G_CONNECT_DEFAULT); _ = c.g_signal_connect_data(im_context, "preedit-end", c.G_CALLBACK(>kInputPreeditEnd), self, null, c.G_CONNECT_DEFAULT); @@ -1523,6 +1525,22 @@ fn gtkMouseLeave( }; } +fn gtkMouseScrollPrecisionBegin( + _: *c.GtkEventControllerScroll, + ud: ?*anyopaque, +) callconv(.C) void { + const self = userdataSelf(ud.?); + self.precision_scroll = true; +} + +fn gtkMouseScrollPrecisionEnd( + _: *c.GtkEventControllerScroll, + ud: ?*anyopaque, +) callconv(.C) void { + const self = userdataSelf(ud.?); + self.precision_scroll = false; +} + fn gtkMouseScroll( _: *c.GtkEventControllerScroll, x: c.gdouble, @@ -1533,15 +1551,17 @@ fn gtkMouseScroll( const scaled = self.scaledCoordinates(x, y); // GTK doesn't support any of the scroll mods. - const scroll_mods: input.ScrollMods = .{}; + const scroll_mods: input.ScrollMods = .{ .precision = self.precision_scroll }; + // Multiply precision scrolls by 10 to get a better response from touchpad scrolling + const multiplier: f64 = if (self.precision_scroll) 10 else 1; self.core_surface.scrollCallback( // We invert because we apply natural scrolling to the values. // This behavior has existed for years without Linux users complaining // but I suspect we'll have to make this configurable in the future // or read a system setting. - scaled.x * -1, - scaled.y * -1, + scaled.x * -1 * multiplier, + scaled.y * -1 * multiplier, scroll_mods, ) catch |err| { log.err("error in scroll callback err={}", .{err}); diff --git a/src/config/Config.zig b/src/config/Config.zig index d2c033cdc..c2749b45c 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -609,10 +609,8 @@ palette: Palette = .{}, /// than 0.01 or greater than 10,000 will be clamped to the nearest valid /// value. /// -/// A value of "1" (default) scrolls the default amount. A value of "2" scrolls -/// double the default amount. A value of "0.5" scrolls half the default amount. -/// Et cetera. -@"mouse-scroll-multiplier": f64 = 1.0, +/// A value of "3" (default) scrolls 3 lines per tick. +@"mouse-scroll-multiplier": f64 = 3.0, /// The opacity level (opposite of transparency) of the background. A value of /// 1 is fully opaque and a value of 0 is fully transparent. A value less than 0