From 6cd33661064df4163f3bd98db432526ad2165b7f Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Fri, 31 Jul 2026 03:15:28 -0300 Subject: a mostly-vertical touchpad swipe stays vertical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pad faithfully turns a two-finger scroll's sideways drift into wheel_left and wheel_right, so a plain scroll slid the view sideways underneath you. Every vertical tick now re-arms a guard and every horizontal tick spends one instead of scrolling, so horizontal has to EARN its way back by landing three ticks in a row with no vertical among them. Clock-free on purpose: the core is a state machine with no timestamp on a mouse event, and faking one by counting renders would be worse than the counter. The guard is only ever armed BY vertical scrolling, so a horizontal swipe from a still view still moves on its first tick — only horizontal that interrupts vertical has anything to prove. A tilt wheel gets the same treatment, where recent-vertical is a much weaker signal of accident. Deliberate: the only honest fix is a per-device flag out of the shell, and that layer costs more than the three clicks it would save. The rule is one pure function next to the number it reads, with an inline test written to hold for any tuning of that number. unit-test grew a fourth binary over the core module hxdiff already links. New golden wheeldrift; none moved. --- src/pardes.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index cd01187b..93ea1016 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -993,6 +993,16 @@ pub const Pardes = struct { drag: Drag = .none, hover_col: u16 = 0, hover_row: u16 = 0, + /// touchpad drift guard, counted down in horizontal wheel ticks — see + /// config.wheelTick, which owns the whole rule. Global, and clock-free on + /// purpose: the core has no clock, so "recently scrolled vertically" can + /// only mean "in the last few wheel events", which is all the heuristic + /// needs. + /// ponytail: it therefore never times out — only a horizontal tick spends + /// it, so a sideways swipe an hour after a scroll still pays the toll. If + /// that ever bites, clear it on any non-wheel event: a keypress or a click + /// is proof the gesture ended, and still needs no clock. + wheel_guard: u8 = 0, ctrl_w_pending: bool = false, /// SPC leader in flight, holding the key path typed so far (empty = just /// SPC). Global like ctrl_w_pending — there is one leader and it acts on @@ -4292,11 +4302,19 @@ pub const Pardes = struct { .none => {}, // hover tracked above; motion has no other meaning .wheel_up, .wheel_down => { if (m.kind != .press) return; + // re-arms the drift guard below; vertical always scrolls, so + // the answer is never in doubt here + _ = config.wheelTick(&p.wheel_guard, true); const id = hovered orelse return; p.panes[id].?.scrollBy(if (m.button == .wheel_up) -config.wheel_rows else config.wheel_rows); }, .wheel_left, .wheel_right => { if (m.kind != .press) return; + // a mostly-vertical two-finger swipe's sideways drift dies + // here rather than sliding the view out from under a scroll. + // Charged against the gesture, not the pane, so it runs before + // we ask what is hovered. + if (!config.wheelTick(&p.wheel_guard, false)) return; const id = hovered orelse return; const pane = p.panes[id].?; // ponytail: no right clamp — overscroll shows blank and the -- cgit v1.3