fix: accumulate fractional wheel-line scroll deltas

This commit is contained in:
Frederic Laing 2026-06-30 21:39:17 +02:00 committed by Jacob Kauffmann
parent 1d3d8837df
commit 1e49511fcf

View file

@ -1553,16 +1553,22 @@ where
} else {
match delta {
ScrollDelta::Lines { x: _, y } => {
//TODO: this adjustment is just a guess!
// High-resolution wheels deliver a notch as many
// fractional deltas; accumulate them so nothing
// below one whole line is lost (see
// accumulate_wheel_lines).
state.scroll_pixels = 0.0;
let lines = (-y * 6.0) as i32;
let (lines, remainder) =
accumulate_wheel_lines(*y, state.scroll_lines);
state.scroll_lines = remainder;
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(-lines));
terminal.scroll(TerminalScroll::Delta(lines));
}
shell.capture_event();
}
ScrollDelta::Pixels { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_lines = 0.0;
state.scroll_pixels -= y * 6.0;
let mut lines = 0;
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
@ -1728,6 +1734,26 @@ fn update_active_regex_match(
}
}
/// Whole lines scrolled per unit of wheel `Lines` delta.
//TODO: this adjustment is just a guess!
const SCROLL_LINE_MULTIPLIER: f32 = 6.0;
/// Convert a wheel `Lines` delta into whole lines to scroll, accumulating the
/// fractional remainder across events.
///
/// High-resolution wheels deliver a single physical notch as many fractional
/// deltas (e.g. `y = 0.125`). Scaling by [`SCROLL_LINE_MULTIPLIER`] and
/// truncating per event discards anything below one whole line, so most of
/// those events would scroll nothing. Carrying the remainder forward means the
/// fractions add up and a full notch scrolls the intended amount.
///
/// Returns `(whole_lines, new_accumulator)`.
fn accumulate_wheel_lines(y: f32, accumulator: f32) -> (i32, f32) {
let total = accumulator + y * SCROLL_LINE_MULTIPLIER;
let lines = total.trunc() as i32;
(lines, total - lines as f32)
}
fn shade(color: cosmic_text::Color, is_focused: bool) -> cosmic_text::Color {
if is_focused {
color
@ -1929,6 +1955,7 @@ pub struct State {
dragging: Option<Dragging>,
is_focused: bool,
scroll_pixels: f32,
scroll_lines: f32,
scrollbar_rect: Cell<Rectangle<f32>>,
autoscroll: DragAutoscroll,
preedit: Option<input_method::Preedit>,
@ -1943,6 +1970,7 @@ impl State {
dragging: None,
is_focused: false,
scroll_pixels: 0.0,
scroll_lines: 0.0,
scrollbar_rect: Cell::new(Rectangle::default()),
autoscroll: DragAutoscroll::new(AUTOSCROLL_INTERVAL),
preedit: None,
@ -2022,7 +2050,38 @@ fn ss3(code: &str, modifiers: u8) -> Option<Vec<u8>> {
#[cfg(test)]
mod tests {
use super::{EdgeScrollDirection, edge_scroll_adjustment};
use super::{EdgeScrollDirection, accumulate_wheel_lines, edge_scroll_adjustment};
#[test]
fn wheel_lines_single_fractional_event_keeps_remainder() {
// A high-res wheel notch arrives as fractional deltas. A lone 0.125
// event is less than one whole line, but must not be discarded.
let (lines, remainder) = accumulate_wheel_lines(0.125, 0.0);
assert_eq!(lines, 0);
assert!((remainder - 0.75).abs() < f32::EPSILON);
}
#[test]
fn wheel_lines_accumulate_into_whole_lines() {
// Eight 0.125 deltas make up one physical notch and must scroll the
// full SCROLL_LINE_MULTIPLIER (6) lines, not be lost to truncation.
let mut accumulator = 0.0;
let mut total = 0;
for _ in 0..8 {
let (lines, remainder) = accumulate_wheel_lines(0.125, accumulator);
accumulator = remainder;
total += lines;
}
assert_eq!(total, 6);
}
#[test]
fn wheel_lines_preserve_direction() {
let (down, _) = accumulate_wheel_lines(0.25, 0.0);
let (up, _) = accumulate_wheel_lines(-0.25, 0.0);
assert_eq!(down, 1);
assert_eq!(up, -1);
}
const BUFFER_HEIGHT: f32 = 200.0;
const CELL_HEIGHT: f32 = 20.0;