diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.zig | 56 | ||||
| -rw-r--r-- | src/pardes.zig | 18 |
2 files changed, 74 insertions, 0 deletions
diff --git a/src/config.zig b/src/config.zig index 35be4b9f..7f83155a 100644 --- a/src/config.zig +++ b/src/config.zig @@ -303,6 +303,62 @@ pub const search_submit: []const Chord = &.{.{ .cp = Key.enter }}; pub const wheel_rows: i32 = 1; pub const wheel_cols: i32 = 4; +/// Touchpad drift guard, in ticks. A two-finger swipe that is MEANT to be +/// vertical carries a little sideways drift, and the pad faithfully turns that +/// drift into wheel_left/wheel_right — so a plain scroll slides the view +/// sideways under you. Every vertical tick re-arms the guard to this many +/// ticks and every horizontal tick spends one instead of scrolling, which +/// makes horizontal EARN its way back: it has to land this many ticks in a row +/// with no vertical among them. 3, because drift arrives in ones and twos — +/// at 1 or 2 a doubled drift tick mid-swipe still gets through, and much +/// higher starts eating deliberate swipes. Set 0 to disable the heuristic. +/// +/// It costs nothing at rest: the guard is only armed by vertical scrolling, so +/// a horizontal swipe that starts from a still view moves on its FIRST tick. +/// Note this applies to a tilt wheel too, where "recent vertical" is a much +/// weaker signal of accident — a mouse would rather not have it. Living with +/// that is deliberate: the only honest fix is a per-device flag out of the +/// shell (libinput/SDL know which is which, vaxis does not), and paying for a +/// device-detection layer to spare a tilt wheel three clicks after a scroll is +/// a worse trade than the three clicks. +pub const wheel_guard_ticks: u8 = 3; + +/// The whole guard, as one state machine, so it can be tested as one thing: +/// fold a wheel tick into `guard` and answer whether it scrolls. The core owns +/// the counter (Pardes.wheel_guard) because the gesture belongs to the DEVICE, +/// not to whichever pane the pointer happens to sit over. +pub fn wheelTick(guard: *u8, vertical: bool) bool { + if (vertical) { + guard.* = wheel_guard_ticks; + return true; + } + if (guard.* == 0) return true; + guard.* -= 1; + return false; +} + +// written to hold for ANY tuning of wheel_guard_ticks, since tuning it by hand +// is what this file is for — a test that pinned the number 3 would just be a +// second place to edit it +test "wheel drift guard" { + var g: u8 = 0; + // from rest, horizontal moves on the first tick — nothing to prove + try std.testing.expect(wheelTick(&g, false)); + try std.testing.expect(wheelTick(&g, false)); + if (wheel_guard_ticks == 0) return; // guard disabled: nothing left to check + // a vertical swipe with drift mixed in: every sideways tick is swallowed, + // because each vertical tick re-arms the guard in full + for (0..4) |_| try std.testing.expect(wheelTick(&g, true)); + try std.testing.expect(!wheelTick(&g, false)); + try std.testing.expect(wheelTick(&g, true)); + try std.testing.expect(!wheelTick(&g, false)); + // the deliberate horizontal swipe that follows pays off the rest of the + // guard tick by tick, then runs free + for (1..wheel_guard_ticks) |_| try std.testing.expect(!wheelTick(&g, false)); + try std.testing.expect(wheelTick(&g, false)); + try std.testing.expect(wheelTick(&g, false)); +} + // ---- layout numbers that are taste ---- /// the file pane's line-number gutter, in columns 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 |
