Scroll by less than a full cell #3206
Replies: 5 comments 10 replies
-
|
also would really like to see this feature 😄 |
Beta Was this translation helpful? Give feedback.
-
|
This would make for a really smooth experience, both on laptops with touchpads (or discrete touchpad) but also for devices that have high-resolution scrolling capabilities. These aren't too common, but some open-source trackballs (and keyboards with trackballs) use high-resolution sensors and have started adding this functionality. Whether or not something will handle the high-resolution scrolling events is hit-or-miss—Chromium (etc.) and Nautilus do, but Firefox and GNOME terminal don't. FTR, GNOME terminal does scroll smoothly with touchpads, just not with high-resolution scroll wheels (or devices presenting as such). I don't think any of the high-performance GPU-rendered terminals do this at all... I even seem to remember one of them flat out refusing. In case it's of interest, GNOME terminal lets you scroll smoothly (again, on a touchpad) through the shell history, but I think that's the extent of it. It doesn't affect any applications; e.g., in (n)vim, scrolling is discretized to number of lines, since (AIUI) the application either only receives or only understands regular scroll detents and can't scroll in fractions of lines. |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
|
I made a hacky version of this using shaders and a ghostty patch Screen.Recording.2025-08-25.at.8.40.35.PM.movdiff --git a/src/Surface.zig b/src/Surface.zig
index 96aaf84d8..85e8abcaa 100644
--- a/src/Surface.zig
+++ b/src/Surface.zig
@@ -3252,13 +3252,12 @@ pub fn scrollCallback(
break :y .{};
}
- // We scroll by the number of rows in the offset and save the remainder
- const amount = poff / cell_size;
+ // We scroll by the number of rows in the offset, rounded towards zero and save the remainder
+ const amount: f64 = @trunc(poff / cell_size);
assert(@abs(amount) >= 1);
self.mouse.pending_scroll_y = poff - (amount * cell_size);
- // Round towards zero.
- const delta: isize = @intFromFloat(@trunc(amount));
+ const delta: isize = @intFromFloat(amount);
assert(@abs(delta) >= 1);
break :y .{ .delta = delta };
@@ -3278,10 +3277,10 @@ pub fn scrollCallback(
break :x .{};
}
- const amount = poff / cell_size;
+ const amount: f64 = @trunc(poff / cell_size);
assert(@abs(amount) >= 1);
self.mouse.pending_scroll_x = poff - (amount * cell_size);
- const delta: isize = @intFromFloat(@trunc(amount));
+ const delta: isize = @intFromFloat(amount);
assert(@abs(delta) >= 1);
break :x .{ .delta = delta };
};
diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig
index 39eec7b43..ce144b1fa 100644
--- a/src/renderer/generic.zig
+++ b/src/renderer/generic.zig
@@ -746,6 +746,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.current_cursor_color = @splat(0),
.previous_cursor_color = @splat(0),
.cursor_change_time = 0,
+ .pending_scroll = @splat(0),
},
.bg_image_buffer = undefined,
@@ -2287,6 +2288,22 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
0,
};
+ const surface: *Surface = @fieldParentPtr("renderer", self);
+ var pending_y = surface.mouse.pending_scroll_y;
+ if (pending_y != 0) {
+ if (surface.io.terminal.flags.mouse_event != .none) {
+ pending_y = 0;
+ } else {
+ const top_left = surface.io.terminal.screens.active.pages.getTopLeft(.viewport);
+ if (pending_y > 0 and top_left.up(1) == null) pending_y = 0;
+ if (pending_y < 0 and surface.io.terminal.screens.active.pages.pinIsActive(top_left)) pending_y = 0;
+ }
+ }
+ self.custom_shader_uniforms.pending_scroll = .{
+ 0,
+ @floatCast(pending_y),
+ };
+
// Update custom cursor uniforms, if we have a cursor.
if (self.cells.getCursorGlyph()) |cursor| {
const cursor_width: f32 = @floatFromInt(cursor.glyph_size[0]);
diff --git a/src/renderer/shaders/shadertoy_prefix.glsl b/src/renderer/shaders/shadertoy_prefix.glsl
index 6d9cf0f68..689edad0c 100644
--- a/src/renderer/shaders/shadertoy_prefix.glsl
+++ b/src/renderer/shaders/shadertoy_prefix.glsl
@@ -16,6 +16,7 @@ layout(binding = 1, std140) uniform Globals {
uniform vec4 iCurrentCursorColor;
uniform vec4 iPreviousCursorColor;
uniform float iTimeCursorChange;
+ uniform vec2 iPendingScroll;
};
layout(binding = 0) uniform sampler2D iChannel0;
diff --git a/src/renderer/shadertoy.zig b/src/renderer/shadertoy.zig
index 0d096c0fc..d6cd8f4f6 100644
--- a/src/renderer/shadertoy.zig
+++ b/src/renderer/shadertoy.zig
@@ -25,6 +25,7 @@ pub const Uniforms = extern struct {
current_cursor_color: [4]f32 align(16),
previous_cursor_color: [4]f32 align(16),
cursor_change_time: f32 align(4),
+ pending_scroll: [2]f32 align(8),
};
/// The target to load shaders for.
diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig
index 9e14e2a75..90880feb5 100644
--- a/src/terminal/PageList.zig
+++ b/src/terminal/PageList.zig
@@ -3223,7 +3223,7 @@ pub fn pinIsValid(self: *const PageList, p: Pin) bool {
/// Returns the viewport for the given pin, preferring to pin to
/// "active" if the pin is within the active area.
-fn pinIsActive(self: *const PageList, p: Pin) bool {
+pub fn pinIsActive(self: *const PageList, p: Pin) bool {
// If the pin is in the active page, then we can quickly determine
// if we're beyond the end.
const active = self.getTopLeft(.active);// smoothscroll.glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
fragCoord -= iPendingScroll;
fragColor = texture(iChannel0, fragCoord / iResolution.xy);
}What it would need:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Few terminals do this but I like it when they do. When scrolling on a trackpad, it would be nice to be able to stop partway through a cell rather than having the minimum scroll amount being a full cell (except when scrolling is handled by an application, like vim).
my proposal: (xfce4-terminal and some others do this)
EXAMPLE.SMOOTH.mp4
current scrolling:
EXAMPLE.DISCRETE.mp4
I looked at this a little bit and it doesn't seem like it should be too hard to implement, there's already a property for pending_scroll_y - it changing needs to trigger a rerender, have the value passed through to rendering, ask the shader to vertically offset the result by that amount, and also maybe render an extra line above/below and clip so that there's never blank space that turns into a line when you get past a certain scroll point.
Beta Was this translation helpful? Give feedback.
All reactions