summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.zig56
-rw-r--r--src/pardes.zig18
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