diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/detached/wire.zig | 2 | ||||
| -rw-r--r-- | src/gui/gui.zig | 2 | ||||
| -rw-r--r-- | src/macos.zig | 4 | ||||
| -rw-r--r-- | src/macos/Sources/PardesView.swift | 24 | ||||
| -rw-r--r-- | src/macos/pardes.h | 2 | ||||
| -rw-r--r-- | src/pardes.zig | 31 | ||||
| -rw-r--r-- | src/web.zig | 2 | ||||
| -rw-r--r-- | src/web/app.mjs | 27 |
8 files changed, 83 insertions, 11 deletions
diff --git a/src/detached/wire.zig b/src/detached/wire.zig index ab19bcfb..989991e0 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -81,6 +81,8 @@ const ButtonTag = enum(u8) { wheel_left = 0x05, wheel_right = 0x06, none = 0x07, + back = 0x08, + forward = 0x09, }; const KindTag = enum(u8) { press = 0x00, release = 0x01, motion = 0x02, drag = 0x03 }; diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 082c8159..a0f0a3e4 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -3029,6 +3029,8 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { 1 => .left, 2 => .middle, 3 => .right, + 4 => .back, + 5 => .forward, else => return, }; g.pad_x = b.x; diff --git a/src/macos.zig b/src/macos.zig index 96598b90..5c523acd 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -1490,6 +1490,8 @@ export fn pardes_mouse(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, m 5 => .wheel_left, 6 => .wheel_right, 7 => .none, + 8 => .back, + 9 => .forward, else => return, }; const kind: pardes.Mouse.Kind = switch (kind_arg) { @@ -2365,6 +2367,8 @@ test "pardes.h matches the Zig boundary" { try expectEqual(c.PARDES_MOUSE_WHEEL_LEFT, @intFromEnum(pardes.Mouse.Button.wheel_left)); try expectEqual(c.PARDES_MOUSE_WHEEL_RIGHT, @intFromEnum(pardes.Mouse.Button.wheel_right)); try expectEqual(c.PARDES_MOUSE_NONE, @intFromEnum(pardes.Mouse.Button.none)); + try expectEqual(c.PARDES_MOUSE_BACK, @intFromEnum(pardes.Mouse.Button.back)); + try expectEqual(c.PARDES_MOUSE_FORWARD, @intFromEnum(pardes.Mouse.Button.forward)); try expectEqual(c.PARDES_MOUSE_PRESS, @intFromEnum(pardes.Mouse.Kind.press)); try expectEqual(c.PARDES_MOUSE_RELEASE, @intFromEnum(pardes.Mouse.Kind.release)); try expectEqual(c.PARDES_MOUSE_MOTION, @intFromEnum(pardes.Mouse.Kind.motion)); diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift index 5c5bc8ba..bfc8a060 100644 --- a/src/macos/Sources/PardesView.swift +++ b/src/macos/Sources/PardesView.swift @@ -1730,11 +1730,25 @@ final class PardesView: NSView { override func rightMouseDown(with event: NSEvent) { beginClick(PARDES_MOUSE_RIGHT, event) } override func rightMouseDragged(with event: NSEvent) { continueClick(event) } override func rightMouseUp(with event: NSEvent) { endClick(event) } - // otherMouse covers button 2 and up. acme's vocabulary stops at three, so - // every one of them lands on middle rather than being invented into a fourth. - override func otherMouseDown(with event: NSEvent) { beginClick(PARDES_MOUSE_MIDDLE, event) } - override func otherMouseDragged(with event: NSEvent) { continueClick(event) } - override func otherMouseUp(with event: NSEvent) { endClick(event) } + // Thumb navigation has its own stream so it cannot replace a held + // selection button or inherit a resting trackpad's finger count. + override func otherMouseDown(with event: NSEvent) { + if event.buttonNumber == 2 { beginClick(PARDES_MOUSE_MIDDLE, event); return } + guard event.buttonNumber == 3 || event.buttonNumber == 4, + let at = cell(for: event) else { return } + press(event.buttonNumber == 3 ? PARDES_MOUSE_BACK : PARDES_MOUSE_FORWARD, + at: at, mods: pointerModifiers) + } + override func otherMouseDragged(with event: NSEvent) { + if event.buttonNumber == 2 { continueClick(event) } + } + override func otherMouseUp(with event: NSEvent) { + if event.buttonNumber == 2 { endClick(event); return } + guard event.buttonNumber == 3 || event.buttonNumber == 4, + let at = cell(for: event) else { return } + release(event.buttonNumber == 3 ? PARDES_MOUSE_BACK : PARDES_MOUSE_FORWARD, + at: at, mods: pointerModifiers) + } /// A deep press, which is a second gesture layered on the click already in /// flight. AppKit keeps sending stage-2 events while the finger stays down, diff --git a/src/macos/pardes.h b/src/macos/pardes.h index 845c8096..546273d9 100644 --- a/src/macos/pardes.h +++ b/src/macos/pardes.h @@ -167,6 +167,8 @@ typedef enum { PARDES_MOUSE_WHEEL_LEFT = 5, PARDES_MOUSE_WHEEL_RIGHT = 6, PARDES_MOUSE_NONE = 7, + PARDES_MOUSE_BACK = 8, + PARDES_MOUSE_FORWARD = 9, } pardes_mouse_button_e; typedef enum { diff --git a/src/pardes.zig b/src/pardes.zig index 1dbe2e81..efd82474 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -4314,7 +4314,7 @@ test "normal header Enter executes its command rather than searching for it" { pub const Mouse = struct { // sel indexing relies on left/middle/right being 0/1/2 - pub const Button = enum { left, middle, right, wheel_up, wheel_down, wheel_left, wheel_right, none }; + pub const Button = enum { left, middle, right, wheel_up, wheel_down, wheel_left, wheel_right, none, back, forward }; pub const Kind = enum { press, release, motion, drag }; button: Button, kind: Kind, @@ -10747,6 +10747,10 @@ pub const Pardes = struct { } fn handleMouse(p: *Pardes, m: Mouse) void { + if (m.button == .back or m.button == .forward) { + if (m.kind == .press) p.jumpBy(if (m.button == .back) -1 else 1); + return; + } const raw_col = @min(m.col, p.screen_w -| 1); const raw_row = @min(m.row, p.screen_h -| 1); p.pointer_raw_col = raw_col; @@ -10842,7 +10846,7 @@ pub const Pardes = struct { } switch (m.button) { - .none => {}, // hover tracked above; motion has no other meaning + .none, .back, .forward => {}, // hover and navigation handled above .wheel_up, .wheel_down => { if (m.kind != .press) return; // re-arms the drift guard below; vertical always scrolls, so @@ -13948,3 +13952,26 @@ test "jump history terminal rectangle follows output and clears on reflow" { try std.testing.expect(pane.pointerSelection(pane.sel[0]) == null); try std.testing.expectEqual(.none, pane.sel[0].state); } + +test "mouse thumb buttons navigate once without disturbing selection gestures" { + const p = try Pardes.init(std.testing.allocator, .{ .cols = 60, .rows = 12 }); + defer p.deinit(); + const pane = try p.setTestFile("alpha beta\ngamma delta\nepsilon zeta\n"); + pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true); + const selection = pane.primaryRange(pane.file.?.content, 0); + p.njumps = 3; + p.jcur = 2; + for (0..3) |i| p.jumps[i] = .{ .pane = 0, .serial = pane.serial, .line = @intCast(i + 1), .col = 1 }; + p.drag = .{ .select = .{ .id = 0, .button = config.select_button } }; + const drag = p.drag; + p.update(.{ .mouse = .{ .button = .back, .kind = .press, .col = 0, .row = 0 } }); + try std.testing.expectEqual(@as(usize, 1), p.jcur); + for ([_]Mouse.Kind{ .drag, .release, .motion }) |kind| + p.update(.{ .mouse = .{ .button = .back, .kind = kind, .col = 0, .row = 0 } }); + try std.testing.expectEqual(@as(usize, 1), p.jcur); + p.update(.{ .mouse = .{ .button = .forward, .kind = .press, .col = 0, .row = 0 } }); + p.update(.{ .mouse = .{ .button = .forward, .kind = .release, .col = 0, .row = 0 } }); + try std.testing.expectEqual(@as(usize, 2), p.jcur); + try std.testing.expectEqualDeep(selection, pane.primaryRange(pane.file.?.content, 0)); + try std.testing.expectEqualDeep(drag, p.drag); +} diff --git a/src/web.zig b/src/web.zig index 3a9446b6..bceaaeeb 100644 --- a/src/web.zig +++ b/src/web.zig @@ -217,6 +217,8 @@ export fn pardes_mouse(button_arg: u32, kind_arg: u32, col_arg: u32, row_arg: u3 5 => .wheel_left, 6 => .wheel_right, 7 => .none, + 8 => .back, + 9 => .forward, else => return, }; const kind: pardes.Mouse.Kind = switch (kind_arg) { diff --git a/src/web/app.mjs b/src/web/app.mjs index fcb9c23d..be61de89 100644 --- a/src/web/app.mjs +++ b/src/web/app.mjs @@ -7,7 +7,7 @@ const DEFAULT_BG = "#121212"; const decoder = new TextDecoder(); const encoder = new TextEncoder(); -const BUTTON = Object.freeze({ left: 0, middle: 1, right: 2, wheelUp: 3, wheelDown: 4, wheelLeft: 5, wheelRight: 6, none: 7 }); +const BUTTON = Object.freeze({ left: 0, middle: 1, right: 2, wheelUp: 3, wheelDown: 4, wheelLeft: 5, wheelRight: 6, none: 7, back: 8, forward: 9 }); const MOUSE = Object.freeze({ press: 0, release: 1, motion: 2, drag: 3 }); const SPECIAL_KEYS = Object.freeze({ Enter: 0x0d, @@ -214,11 +214,22 @@ export class PardesRuntime { return { x: rect.left + (col + 0.5) * this.cellWidth, y: rect.top + (row + 0.5) * this.cellHeight }; } - pointerDown(event) { + mouseDown(event) { event.preventDefault(); + if (event.button !== 3 && event.button !== 4) return; + this.root.focus({ preventScroll: true }); + const point = this.point(event); + this.wasm.pardes_mouse(pointerButton(event.button), MOUSE.press, point.col, point.row, modifiers(event)); + } + + pointerDown(event) { + // Mouse defaults are cancelled by mousedown. Cancelling pointerdown + // would suppress that stream, including thumb presses while left is held. + if (event.button === 3 || event.button === 4) return; + if (event.pointerType === "touch") event.preventDefault(); this.root.focus({ preventScroll: true }); - this.root.setPointerCapture?.(event.pointerId); const point = this.point(event); + this.root.setPointerCapture?.(event.pointerId); if (event.pointerType === "touch") { const packed = this.wasm.pardes_chrome_target(point.col, point.row) >>> 0; const chrome = packed !== 0xffffffff; @@ -233,6 +244,7 @@ export class PardesRuntime { } pointerMove(event) { + if (event.button === 3 || event.button === 4) return; const point = this.point(event); const active = this.pointer?.id === event.pointerId ? this.pointer : null; if (!active) { @@ -268,6 +280,10 @@ export class PardesRuntime { } pointerUp(event) { + if (event.button === 3 || event.button === 4) { + event.preventDefault(); + return; + } const active = this.pointer?.id === event.pointerId ? this.pointer : null; if (!active) return; event.preventDefault(); @@ -303,6 +319,9 @@ export class PardesRuntime { this.root.addEventListener("keydown", (event) => this.key(event)); this.root.addEventListener("paste", (event) => this.paste(event)); this.root.addEventListener("contextmenu", (event) => event.preventDefault()); + this.root.addEventListener("auxclick", (event) => event.preventDefault()); + // Mouse events retain additional button presses while another button is held. + this.root.addEventListener("mousedown", (event) => this.mouseDown(event)); this.root.addEventListener("pointerdown", (event) => this.pointerDown(event)); this.root.addEventListener("pointermove", (event) => this.pointerMove(event)); this.root.addEventListener("pointerup", (event) => this.pointerUp(event)); @@ -453,7 +472,7 @@ function modifiers(event) { } function pointerButton(button) { - return button === 1 ? BUTTON.middle : button === 2 ? BUTTON.right : BUTTON.left; + return button === 1 ? BUTTON.middle : button === 2 ? BUTTON.right : button === 3 ? BUTTON.back : button === 4 ? BUTTON.forward : BUTTON.left; } function clamp(value, low, high) { |
