a14ea705
fix: a click finds its pane by column, not just row
a73x 2026-08-25 09:10
Commit message
src/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -484,16 +484,18 @@ const Tile = struct { | |||
| 484 | } | 484 | } |
| 485 | }; | 485 | }; |
| 486 | 486 | ||
| 487 | /// Which tile's rectangle a zero-based terminal row falls in, or null | 487 | /// Which tile's rectangle a zero-based terminal (row, col) falls in, or |
| 488 | /// when none does. The keyboard routes a click to the tile it lands in so | 488 | /// null when none does. A beside layout gives every tile the same rows, so |
| 489 | /// focus can follow it; the drag itself is the focused tile's Core's. | 489 | /// the column is what separates them; a click on a rail column hits no |
| 490 | /// Under `paint_mu` because `Tile.rect` is written under it. | 490 | /// tile and focus stays put. Under `paint_mu` because `Tile.rect` is |
| 491 | fn rectHit(tiles: []Tile, present: []const bool, shared: *Shared, row: u16) ?usize { | 491 | /// written under it. |
| 492 | fn rectHit(tiles: []Tile, present: []const bool, shared: *Shared, row: u16, col: u16) ?usize { | ||
| 492 | shared.paint_mu.lock(); | 493 | shared.paint_mu.lock(); |
| 493 | defer shared.paint_mu.unlock(); | 494 | defer shared.paint_mu.unlock(); |
| 494 | for (tiles, present) |*t, p| { | 495 | for (tiles, present) |*t, p| { |
| 495 | if (!p) continue; | 496 | if (!p) continue; |
| 496 | if (row >= t.rect.top and row < t.rect.top + t.rect.rows) return t.idx; | 497 | if (row >= t.rect.top and row < t.rect.top + t.rect.rows and |
| 498 | col >= t.rect.left and col < t.rect.left + t.rect.cols) return t.idx; | ||
| 497 | } | 499 | } |
| 498 | return null; | 500 | return null; |
| 499 | } | 501 | } |
| @@ -2794,7 +2796,7 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2794 | var seg_start: usize = 0; | 2796 | var seg_start: usize = 0; |
| 2795 | for (report.events) |ev| { | 2797 | for (report.events) |ev| { |
| 2796 | if (ev.kind != .press) continue; | 2798 | if (ev.kind != .press) continue; |
| 2797 | if (rectHit(tiles[0..live], present[0..live], &shared, ev.row)) |hit| { | 2799 | if (rectHit(tiles[0..live], present[0..live], &shared, ev.row, ev.col)) |hit| { |
| 2798 | if (hit != shared.sel) { | 2800 | if (hit != shared.sel) { |
| 2799 | if (ev.at > seg_start) | 2801 | if (ev.at > seg_start) |
| 2800 | sendKeys(&tiles[shared.sel], cmd.forward[seg_start..ev.at]); | 2802 | sendKeys(&tiles[shared.sel], cmd.forward[seg_start..ev.at]); |
| @@ -3655,7 +3657,7 @@ test "bare keys reach the focused tile; the prefix does not" { | |||
| 3655 | try std.testing.expectEqual(interact.PrefixFilter.Action.next_session, b.action); | 3657 | try std.testing.expectEqual(interact.PrefixFilter.Action.next_session, b.action); |
| 3656 | } | 3658 | } |
| 3657 | 3659 | ||
| 3658 | test "focus follows a click: rectHit picks the tile under a row" { | 3660 | test "focus follows a click: rectHit picks the tile under a row and column" { |
| 3659 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; | 3661 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; |
| 3660 | shared.label_rows = 1; | 3662 | shared.label_rows = 1; |
| 3661 | var tiles: [2]Tile = undefined; | 3663 | var tiles: [2]Tile = undefined; |
| @@ -3677,9 +3679,18 @@ test "focus follows a click: rectHit picks the tile under a row" { | |||
| 3677 | }; | 3679 | }; |
| 3678 | const present = [_]bool{ true, true }; | 3680 | const present = [_]bool{ true, true }; |
| 3679 | // A click in row 3 hits tile 0. | 3681 | // A click in row 3 hits tile 0. |
| 3680 | try std.testing.expectEqual(@as(?usize, 0), rectHit(&tiles, &present, &shared, 3)); | 3682 | try std.testing.expectEqual(@as(?usize, 0), rectHit(&tiles, &present, &shared, 3, 0)); |
| 3681 | // A click in row 15 hits tile 1. | 3683 | // A click in row 15 hits tile 1. |
| 3682 | try std.testing.expectEqual(@as(?usize, 1), rectHit(&tiles, &present, &shared, 15)); | 3684 | try std.testing.expectEqual(@as(?usize, 1), rectHit(&tiles, &present, &shared, 15, 0)); |
| 3685 | |||
| 3686 | // A beside pair: same rows, different columns. A click in the left | ||
| 3687 | // half hits tile 0, the right half hits tile 1, and a rail column | ||
| 3688 | // between them hits neither. | ||
| 3689 | tiles[0].rect = .{ .top = 0, .left = 0, .rows = 24, .cols = 39 }; | ||
| 3690 | tiles[1].rect = .{ .top = 0, .left = 41, .rows = 24, .cols = 39 }; | ||
| 3691 | try std.testing.expectEqual(@as(?usize, 0), rectHit(&tiles, &present, &shared, 5, 10)); | ||
| 3692 | try std.testing.expectEqual(@as(?usize, 1), rectHit(&tiles, &present, &shared, 5, 50)); | ||
| 3693 | try std.testing.expectEqual(@as(?usize, null), rectHit(&tiles, &present, &shared, 5, 40)); | ||
| 3683 | } | 3694 | } |
| 3684 | 3695 | ||
| 3685 | // A transport that swallows every frame: a Core's selection request goes | 3696 | // A transport that swallows every frame: a Core's selection request goes |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -1606,6 +1606,7 @@ cleanup() { | |||
| 1606 | "$OUT.wmcap2" "$OUT.wmcap2.err" "$OUT.wmpc2" "$OUT.wmfa" "$OUT.wmfb" \ | 1606 | "$OUT.wmcap2" "$OUT.wmcap2.err" "$OUT.wmpc2" "$OUT.wmfa" "$OUT.wmfb" \ |
| 1607 | "$OUT.wmcap3" "$OUT.wmcap3.err" "$OUT.wmpc3" \ | 1607 | "$OUT.wmcap3" "$OUT.wmcap3.err" "$OUT.wmpc3" \ |
| 1608 | "$OUT.wmcap4" "$OUT.wmcap4.err" "$OUT.wmpc4" \ | 1608 | "$OUT.wmcap4" "$OUT.wmcap4.err" "$OUT.wmpc4" \ |
| 1609 | "$OUT.wmcap5" "$OUT.wmcap5.err" "$OUT.wmpc5" \ | ||
| 1609 | "$OUT.selcap" "$OUT.selcap.err" "$OUT.selpc" \ | 1610 | "$OUT.selcap" "$OUT.selcap.err" "$OUT.selpc" \ |
| 1610 | "$OUT.wmstop" | 1611 | "$OUT.wmstop" |
| 1611 | # The convergence files a FAILING assert_converged leaves behind | 1612 | # The convergence files a FAILING assert_converged leaves behind |
| @@ -7104,6 +7105,39 @@ grep -q "wm-four" "$OUT.wmfb" && { | |||
| 7104 | cat "$OUT.wmfb"; exit 1; } | 7105 | cat "$OUT.wmfb"; exit 1; } |
| 7105 | ok "a click in a tile's rect focuses it, from wherever the focus was" | 7106 | ok "a click in a tile's rect focuses it, from wherever the focus was" |
| 7106 | 7107 | ||
| 7108 | # A beside layout gives every tile the same rows, so a click that tests | ||
| 7109 | # only the row hits the first present tile every time. A click in the | ||
| 7110 | # RIGHT pane's content — column, not row — is what proves the column | ||
| 7111 | # test in rectHit earns its keep: focus starts on a (the left pane) and | ||
| 7112 | # the click must move it to b (the right pane). | ||
| 7113 | set +e | ||
| 7114 | timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap5" --err "$OUT.wmcap5.err" -- \ | ||
| 7115 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc5" 2>&1 <<'EOF' | ||
| 7116 | expect wmb-pin 20000 | ||
| 7117 | settle 700 20000 | ||
| 7118 | send \x1b[<0;70;9M\x1b[<0;70;9m | ||
| 7119 | settle 700 20000 | ||
| 7120 | send printf 'wm-%s\\n' five\n | ||
| 7121 | expect wm-five 15000 | ||
| 7122 | settle 400 15000 | ||
| 7123 | send \x1cd | ||
| 7124 | waitexit 10000 | ||
| 7125 | EOF | ||
| 7126 | RC=$? | ||
| 7127 | set -e | ||
| 7128 | [ "$RC" -eq 0 ] || { | ||
| 7129 | echo "e2e FAIL: wall click: beside leg exited $RC (did the column click focus b?):" | ||
| 7130 | cat "$OUT.wmpc5"; exit 1; } | ||
| 7131 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 | ||
| 7132 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | ||
| 7133 | grep -q "wm-five" "$OUT.wmfb" || { | ||
| 7134 | echo "e2e FAIL: wall click: a click in the right pane did not focus it:" | ||
| 7135 | cat "$OUT.wmfb"; exit 1; } | ||
| 7136 | grep -q "wm-five" "$OUT.wmfa" && { | ||
| 7137 | echo "e2e FAIL: wall click: the focus stayed on the left pane:" | ||
| 7138 | cat "$OUT.wmfa"; exit 1; } | ||
| 7139 | ok "a click in a beside pane finds it by column, not just row" | ||
| 7140 | |||
| 7107 | # ---- a drag copies, and the copy leaves as OSC 52 ---------------------- | 7141 | # ---- a drag copies, and the copy leaves as OSC 52 ---------------------- |
| 7108 | # | 7142 | # |
| 7109 | # The first end-to-end proof that this feature does what it was asked for. | 7143 | # The first end-to-end proof that this feature does what it was asked for. |
| @@ -8256,8 +8290,8 @@ D62PID="" | |||
| 8256 | rm -rf "$LPDSTATE" | 8290 | rm -rf "$LPDSTATE" |
| 8257 | ok "a corrupted sidecar degrades silently to the default layout" | 8291 | ok "a corrupted sidecar degrades silently to the default layout" |
| 8258 | 8292 | ||
| 8259 | [ "$OK_COUNT" = "71" ] || { | 8293 | [ "$OK_COUNT" = "72" ] || { |
| 8260 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 71 —" | 8294 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 72 —" |
| 8261 | echo " a scenario was added (update the pin) or silently lost" | 8295 | echo " a scenario was added (update the pin) or silently lost" |
| 8262 | exit 1 | 8296 | exit 1 |
| 8263 | } | 8297 | } |