summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig240
1 files changed, 208 insertions, 32 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 045f22d9..1e58d3ba 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -3097,6 +3097,9 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void {
const StdinFeed = struct {
parser: vaxis.Parser = .{},
+ /// Inside the driver's bracketed paste: its keys are the paste's text.
+ in_paste: bool = false,
+ paste: std.ArrayList(u8) = .empty,
cache: vaxis.GraphemeCache = .{},
buf: [1024]u8 = undefined,
fill: usize = 0,
@@ -3139,8 +3142,8 @@ const StdinFeed = struct {
var seq_start: usize = 0;
while (seq_start < len) {
- // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>BEL
- // ESC]777;mouse;wheel;<amount>;<x>;<y>BEL
+ // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>[;<mods>]BEL
+ // ESC]777;mouse;wheel;<amount>;<x>;<y>[;<mods>]BEL
// A mouse event at window pixels, built as SDL would and run
// through dispatch, so a driver exercises the real pointer path
// (pixel to cell, tag and body hits). SGR mouse carries only cells.
@@ -3159,6 +3162,9 @@ const StdinFeed = struct {
const button = std.fmt.parseInt(u8, second, 10) catch 0;
const x = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0;
const y = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0;
+ // An optional fifth field holds modifiers as xterm
+ // counts them: shift 4, meta (alt) 8, ctrl 16.
+ const held = std.fmt.parseInt(u8, parts.next() orelse "0", 10) catch 0;
var sev = std.mem.zeroes(c.SDL_Event);
if (std.mem.eql(u8, kind, "wheel")) {
// ESC]777;mouse;wheel;<y, SDL's sign: up positive>;<x>;<y>BEL:
@@ -3169,6 +3175,9 @@ const StdinFeed = struct {
sev.wheel.integer_y = @intFromFloat(@trunc(amount));
sev.wheel.mouse_x = x;
sev.wheel.mouse_y = y;
+ } else if (std.mem.eql(u8, kind, "focus")) {
+ // ESC]777;mouse;focus;<1 gained, 0 lost>;0;0BEL
+ sev.type = if (button != 0) c.SDL_EVENT_WINDOW_FOCUS_GAINED else c.SDL_EVENT_WINDOW_FOCUS_LOST;
} else if (std.mem.eql(u8, kind, "motion")) {
sev.type = c.SDL_EVENT_MOUSE_MOTION;
sev.motion.x = x;
@@ -3181,7 +3190,14 @@ const StdinFeed = struct {
sev.button.x = x;
sev.button.y = y;
}
+ const before = c.SDL_GetModState();
+ var mods: c.SDL_Keymod = 0;
+ if (held & 4 != 0) mods |= c.SDL_KMOD_LSHIFT;
+ if (held & 8 != 0) mods |= c.SDL_KMOD_LALT;
+ if (held & 16 != 0) mods |= c.SDL_KMOD_LCTRL;
+ c.SDL_SetModState(mods);
dispatch(gp, in, &sev);
+ c.SDL_SetModState(before);
out.n_events += 1;
}
seq_start += mouse_prefix.len + end + 1;
@@ -3228,10 +3244,24 @@ const StdinFeed = struct {
seq_start += result.n;
const event = result.event orelse continue;
switch (event) {
- .key_press => |key| {
+ .key_press => |key| if (f.in_paste) {
+ // A newline reads as Ctrl-J, as the tty's paste has it.
+ const text = key.text orelse if (key.codepoint == vaxis.Key.enter or (key.mods.ctrl and key.codepoint == 'j')) "\n" else if (key.codepoint == vaxis.Key.tab) "\t" else "";
+ f.paste.appendSlice(gpa, text) catch {};
+ } else {
postKey(in, key, if (key.text) |t| f.cache.put(t) else "");
out.n_events += 1;
},
+ .paste_start => {
+ f.in_paste = true;
+ f.paste.clearRetainingCapacity();
+ },
+ .paste_end => {
+ f.in_paste = false;
+ if (f.paste.items.len > 0) in.post(.{ .paste = f.paste.items });
+ f.paste.clearAndFree(gpa);
+ out.n_events += 1;
+ },
.mouse => |m| {
const button: ?pardes.Mouse.Button = switch (m.button) {
.left => .left,
@@ -3256,6 +3286,8 @@ const StdinFeed = struct {
.col = @intCast(@max(m.col, 0)),
.row = @intCast(@max(m.row, 0)),
.ctrl = m.mods.ctrl,
+ .shift = m.mods.shift,
+ .alt = m.mods.alt,
} });
out.n_events += 1;
}
@@ -3412,6 +3444,47 @@ test "the GUI's stdin keys reach a terminal pane as the tty's do: F5, Insert and
try std.testing.expectEqualStrings("\x1b[15~\x1b[2~\x1b[A", written.written());
}
+test "the window's function and editing keys reach a terminal as its cursor keys' mode and keyboard flags say" {
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
+ defer core.deinit();
+ core.panes[0].?.body.mode = .tty;
+ while (core.nextEffect()) |_| {}
+ var in: Input = .{ .core = core };
+ var written: std.Io.Writer.Allocating = .init(gpa);
+ defer written.deinit();
+ const Case = struct { modes: []const u8, sym: c.SDL_Keycode, mod: c.SDL_Keymod = 0, sent: []const u8 };
+ const cases = [_]Case{
+ .{ .modes = "", .sym = c.SDLK_UP, .sent = "\x1b[A" },
+ .{ .modes = "", .sym = c.SDLK_HOME, .sent = "\x1b[H" },
+ .{ .modes = "", .sym = c.SDLK_F1, .sent = "\x1bOP" },
+ .{ .modes = "", .sym = c.SDLK_F5, .sent = "\x1b[15~" },
+ .{ .modes = "", .sym = c.SDLK_F12, .mod = c.SDL_KMOD_LSHIFT, .sent = "\x1b[24;2~" },
+ .{ .modes = "", .sym = c.SDLK_INSERT, .sent = "\x1b[2~" },
+ // DECCKM: less, vim, htop.
+ .{ .modes = "\x1b[?1h", .sym = c.SDLK_UP, .sent = "\x1bOA" },
+ .{ .modes = "", .sym = c.SDLK_END, .sent = "\x1bOF" },
+ .{ .modes = "", .sym = c.SDLK_LEFT, .mod = c.SDL_KMOD_LCTRL, .sent = "\x1b[1;5D" },
+ // codex's kitty flags (>7u): Ctrl-C and Alt-a are unambiguous.
+ // (A bare Escape at a shell prompt is pardes's way out of raw input.)
+ .{ .modes = "\x1b[?1l\x1b[>7u", .sym = 'c', .mod = c.SDL_KMOD_LCTRL, .sent = "\x1b[99;5u" },
+ .{ .modes = "", .sym = 'a', .mod = c.SDL_KMOD_LALT, .sent = "\x1b[97;3u" },
+ // With event types asked for (2), an arrow says it is a press, as
+ // ghostty says it.
+ .{ .modes = "", .sym = c.SDLK_UP, .sent = "\x1b[1;1:1A" },
+ };
+ for (cases) |case| {
+ core.update(.{ .output = .{ .pane = 0, .bytes = case.modes } });
+ while (core.nextEffect()) |_| {}
+ keyDown(&in, case.sym, case.mod);
+ written.clearRetainingCapacity();
+ while (core.nextEffect()) |effect| {
+ if (effect == .write) try written.writer.writeAll(effect.write.bytes.slice());
+ }
+ try std.testing.expectEqualStrings(case.sent, written.written());
+ }
+}
+
const Input = struct {
core: ?*pardes.Pardes = null,
client: ?*detached_client.Client = null,
@@ -3457,6 +3530,8 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
}
switch (sev.type) {
c.SDL_EVENT_QUIT, c.SDL_EVENT_WINDOW_CLOSE_REQUESTED => in.close(),
+ c.SDL_EVENT_WINDOW_FOCUS_GAINED => in.post(.{ .focus = true }),
+ c.SDL_EVENT_WINDOW_FOCUS_LOST => in.post(.{ .focus = false }),
c.SDL_EVENT_WINDOW_MOUSE_LEAVE => {
g.pointer_present = false;
g.pointer_mapped = false;
@@ -3526,18 +3601,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
};
g.pointer_mapped = true;
g.pointer_cell = mc;
- in.post(.{
- .mouse = .{
- .button = button,
- .kind = if (b.down) .press else .release,
- .col = mc.col,
- .row = mc.row,
- .tag_hit = mc.tag_hit,
- .body_hit = mc.body_hit,
- .rule_above = mc.rule_above,
- .ctrl = (c.SDL_GetModState() & c.SDL_KMOD_CTRL) != 0,
- },
- });
+ in.post(.{ .mouse = pointerMouse(button, if (b.down) .press else .release, mc, pointerMods()) });
},
c.SDL_EVENT_MOUSE_MOTION => {
const m = sev.motion;
@@ -3559,14 +3623,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
};
g.pointer_mapped = true;
g.pointer_cell = mc;
- in.post(.{ .mouse = .{
- .button = held orelse .none,
- .kind = if (held != null) .drag else .motion,
- .col = mc.col,
- .row = mc.row,
- .tag_hit = mc.tag_hit,
- .body_hit = mc.body_hit,
- } });
+ in.post(.{ .mouse = pointerMouse(held orelse .none, if (held != null) .drag else .motion, mc, pointerMods()) });
},
c.SDL_EVENT_MOUSE_WHEEL => {
const w = sev.wheel;
@@ -3581,7 +3638,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
};
g.pointer_mapped = true;
g.pointer_cell = mc;
- wheelAt(g, in, .{ .x = w.x, .y = w.y, .integer_y = w.integer_y }, mc);
+ wheelAt(g, in, .{ .x = w.x, .y = w.y, .integer_y = w.integer_y, .mods = pointerMods() }, mc);
},
c.SDL_EVENT_FINGER_DOWN, c.SDL_EVENT_FINGER_MOTION, c.SDL_EVENT_FINGER_UP, c.SDL_EVENT_FINGER_CANCELED => {
const f = sev.tfinger;
@@ -3767,6 +3824,13 @@ fn keyDown(in: *Input, sym: c.SDL_Keycode, mod: c.SDL_Keymod) void {
c.SDLK_PAGEUP => pardes.Key.page_up,
c.SDLK_PAGEDOWN => pardes.Key.page_down,
c.SDLK_DELETE => pardes.Key.delete,
+ c.SDLK_INSERT => pardes.Key.insert,
+ c.SDLK_PRINTSCREEN => pardes.Key.print_screen,
+ c.SDLK_SCROLLLOCK => pardes.Key.scroll_lock,
+ c.SDLK_PAUSE => pardes.Key.pause,
+ c.SDLK_APPLICATION => pardes.Key.menu,
+ c.SDLK_F1...c.SDLK_F12 => pardes.Key.f1 + @as(u21, @intCast(sym - c.SDLK_F1)),
+ c.SDLK_F13...c.SDLK_F24 => pardes.Key.f1 + 12 + @as(u21, @intCast(sym - c.SDLK_F13)),
else => blk: {
if (!(ctrl or alt or gui_mod)) break :blk 0;
if (sym < 128 and sym >= ' ') break :blk @intCast(sym);
@@ -3913,16 +3977,16 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void {
.pane = @intCast(id),
.delta_pixels = -w.y * @as(f32, @floatFromInt(g.cell_h)),
} });
- } else if (pane.isTerminal() and pardes.panes.terminal.ownsWheel(pane)) {
+ } else if (!w.mods.shift and pane.isTerminal() and pardes.panes.terminal.ownsWheel(pane)) {
// The program's (codex, htop, less): every whole
// turn goes to it, no glide, for nothing of ours
- // moves.
+ // moves. Shift keeps the wheel pardes's.
resetScroll(g);
g.program_wheel = accumulateWheelDelta(g.program_wheel, w.y);
while (g.program_wheel >= 1) : (g.program_wheel -= 1)
- in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } });
while (g.program_wheel <= -1) : (g.program_wheel += 1)
- in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } });
} else {
g.scroll_pane = id;
g.scroll_col = mc.col;
@@ -3938,9 +4002,9 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void {
} else {
g.scroll_delta = accumulateWheelDelta(g.scroll_delta, w.y);
while (g.scroll_delta >= 1) : (g.scroll_delta -= 1)
- in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } });
while (g.scroll_delta <= -1) : (g.scroll_delta += 1)
- in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } });
}
}
if (w.x != 0) in.post(.{ .mouse = .{
@@ -3950,10 +4014,37 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void {
.row = mc.row,
.tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
+ .ctrl = w.mods.ctrl,
+ .shift = w.mods.shift,
+ .alt = w.mods.alt,
} });
}
-const WheelTurn = struct { x: f32 = 0, y: f32 = 0, integer_y: i32 = 0 };
+const WheelTurn = struct { x: f32 = 0, y: f32 = 0, integer_y: i32 = 0, mods: PointerMods = .{} };
+
+/// The modifiers held with a pointer event, as the core's Mouse carries them.
+const PointerMods = struct { ctrl: bool = false, shift: bool = false, alt: bool = false };
+
+/// The core's Mouse for an SDL press, release or move over cell `mc`.
+fn pointerMouse(button: pardes.Mouse.Button, kind: pardes.Mouse.Kind, mc: MouseCell, mods: PointerMods) pardes.Mouse {
+ return .{
+ .button = button,
+ .kind = kind,
+ .col = mc.col,
+ .row = mc.row,
+ .tag_hit = mc.tag_hit,
+ .body_hit = mc.body_hit,
+ .rule_above = (kind == .press or kind == .release) and mc.rule_above,
+ .ctrl = mods.ctrl,
+ .shift = mods.shift,
+ .alt = mods.alt,
+ };
+}
+
+fn pointerMods() PointerMods {
+ const m = c.SDL_GetModState();
+ return .{ .ctrl = (m & c.SDL_KMOD_CTRL) != 0, .shift = (m & c.SDL_KMOD_SHIFT) != 0, .alt = (m & c.SDL_KMOD_ALT) != 0 };
+}
fn mouseCellWithGeometry(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32, geometry: WindowGeometry) ?MouseCell {
const physical = windowPointToPixels(geometry, x, y);
@@ -4794,6 +4885,39 @@ test "the headless grid host round-trips a yank back as a paste" {
try std.testing.expectEqual(before + yanked.len, pane.file.?.content.len);
}
+test "the clipboard pasted into a terminal reaches its pty as xterm pastes: bracketed when asked, no ESC[201~ smuggled through" {
+ if (comptime !pardes.panes.terminal.enabled) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 16, .tty_only = true });
+ defer core.deinit();
+ var shell: Shell = undefined;
+ shell.core = core;
+ var sent: std.ArrayList(u8) = .empty;
+ defer sent.deinit(gpa);
+ const take = struct {
+ fn f(core_: *pardes.Pardes, out: *std.ArrayList(u8)) ![]const u8 {
+ out.clearRetainingCapacity();
+ while (core_.nextEffect()) |effect| {
+ if (effect == .write and effect.write.pane == 0) try out.appendSlice(std.testing.allocator, effect.write.bytes.slice());
+ }
+ return out.items;
+ }
+ }.f;
+ _ = try take(core, &sent);
+ // The clipboard is SDL's own with no display to hold one.
+ _ = c.SDL_SetHint(c.SDL_HINT_VIDEO_DRIVER, "dummy");
+ defer _ = c.SDL_ResetHint(c.SDL_HINT_VIDEO_DRIVER);
+ if (!c.SDL_InitSubSystem(c.SDL_INIT_VIDEO)) return error.SkipZigTest;
+ defer c.SDL_QuitSubSystem(c.SDL_INIT_VIDEO);
+ putClipboard(gpa, "ls\x1b[201~; rm -rf ~\nx");
+ readClipboard(&shell);
+ try std.testing.expectEqualStrings("ls [201~; rm -rf ~\rx", try take(core, &sent));
+ core.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?2004h" } });
+ _ = try take(core, &sent);
+ readClipboard(&shell);
+ try std.testing.expectEqualStrings("\x1b[200~ls [201~; rm -rf ~\nx\x1b[201~", try take(core, &sent));
+}
+
fn openLink(ctx: ?*anyopaque, url: []const u8) void {
_ = ctx;
look.openLink(url); // desktop browser
@@ -4918,6 +5042,46 @@ test "a wheel's notch is whole lines, a touchpad's fraction is not" {
try std.testing.expectEqual(@as(?i32, null), notchOf(std.math.nan(f32), 1));
}
+test "a click over codex in a terminal reaches its pty with its modifiers; with Shift it is pardes's" {
+ if (comptime !pardes.panes.terminal.enabled) return error.SkipZigTest;
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 16, .tty_only = true });
+ defer core.deinit();
+ var frame: std.heap.ArenaAllocator = .init(gpa);
+ defer frame.deinit();
+ _ = try core.render(frame.allocator());
+ var in: Input = .{ .core = core };
+ var sent: std.ArrayList(u8) = .empty;
+ defer sent.deinit(gpa);
+ const take = struct {
+ fn f(core_: *pardes.Pardes, out: *std.ArrayList(u8)) ![]const u8 {
+ out.clearRetainingCapacity();
+ while (core_.nextEffect()) |effect| {
+ if (effect == .write and effect.write.pane == 0) try out.appendSlice(std.testing.allocator, effect.write.bytes.slice());
+ }
+ return out.items;
+ }
+ }.f;
+ _ = try take(core, &sent);
+ const pane = core.panes[0].?;
+ const r = core.rects[0];
+ const mc: MouseCell = .{ .col = r.x + config.GUTTER + 5, .row = core.bodyTop(pane, r) + 3 };
+ const next: MouseCell = .{ .col = mc.col + 1, .row = mc.row };
+ core.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?1049h\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h" } });
+ _ = try take(core, &sent);
+ in.post(.{ .mouse = pointerMouse(.none, .motion, mc, .{}) });
+ try std.testing.expectEqualStrings("\x1b[<35;6;4M", try take(core, &sent));
+ in.post(.{ .mouse = pointerMouse(.left, .press, mc, .{ .ctrl = true }) });
+ try std.testing.expectEqualStrings("\x1b[<16;6;4M", try take(core, &sent));
+ in.post(.{ .mouse = pointerMouse(.left, .drag, next, .{}) });
+ try std.testing.expectEqualStrings("\x1b[<32;7;4M", try take(core, &sent));
+ in.post(.{ .mouse = pointerMouse(.left, .release, next, .{}) });
+ try std.testing.expectEqualStrings("\x1b[<0;7;4m", try take(core, &sent));
+ in.post(.{ .mouse = pointerMouse(.middle, .press, mc, .{ .shift = true }) });
+ in.post(.{ .mouse = pointerMouse(.middle, .release, mc, .{ .shift = true }) });
+ try std.testing.expectEqualStrings("", try take(core, &sent));
+}
+
test "a wheel over codex in a terminal reaches its pty as the mouse reports it asked for" {
if (comptime !pardes.panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
@@ -4934,6 +5098,7 @@ test "a wheel over codex in a terminal reaches its pty as the mouse reports it a
g.scroll_notch = 0;
g.program_wheel = 0;
g.glide = .{};
+ defer g.glide.deinit(gpa);
g.cell_h = 20;
var in: Input = .{ .core = core };
var sent: std.ArrayList(u8) = .empty;
@@ -4967,6 +5132,15 @@ test "a wheel over codex in a terminal reaches its pty as the mouse reports it a
try std.testing.expectEqualStrings("", try Sent.take(core, &sent));
wheelAt(&g, &in, .{ .y = 0.5 }, mc);
try std.testing.expectEqualStrings("\x1b[<64;6;4M", try Sent.take(core, &sent));
+ // Alt is meta, 8; Shift keeps the turn pardes's own, a glide.
+ wheelAt(&g, &in, .{ .y = 1, .integer_y = 1, .mods = .{ .alt = true } }, mc);
+ try std.testing.expectEqualStrings("\x1b[<72;6;4M", try Sent.take(core, &sent));
+ wheelAt(&g, &in, .{ .y = 1, .integer_y = 1, .mods = .{ .shift = true } }, mc);
+ try std.testing.expectEqualStrings("", try Sent.take(core, &sent));
+ try std.testing.expectEqual(@as(i32, -1), g.scroll_notch);
+ stepGlide(&g, core, gpa);
+ try std.testing.expectEqualStrings("", try Sent.take(core, &sent));
+ resetScroll(&g);
// Its sign-in screen: mouse off, alternate scroll on, so arrow keys.
core.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1007h" } });
_ = try Sent.take(core, &sent);
@@ -5171,7 +5345,8 @@ fn stepGlide(g: *Gui, core: *pardes.Pardes, gpa: std.mem.Allocator) void {
const down = left > 0;
left += if (down) -1 else 1;
const was = pane.scroll();
- core.update(.{ .mouse = .{ .button = if (down) .wheel_down else .wheel_up, .kind = .press, .col = g.scroll_col, .row = g.scroll_row } });
+ // Shift: pardes's own scroll, never a program's wheel.
+ core.update(.{ .mouse = .{ .button = if (down) .wheel_down else .wheel_up, .kind = .press, .col = g.scroll_col, .row = g.scroll_row, .shift = true } });
if (pane.scroll() == was) break;
moved = true;
}
@@ -5344,6 +5519,7 @@ fn stepScroll(g: *Gui, core: *pardes.Pardes, gpa: std.mem.Allocator) void {
.kind = .press,
.col = g.scroll_col,
.row = g.scroll_row,
+ .shift = true, // pardes's own scroll, never a program's wheel
} });
if (pane.scroll() == was) {
resetScroll(g);