diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-09 01:26:08 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-10 09:17:07 -0300 |
| commit | 09c35b22793153d201fa202b52671a6442e5e2e1 (patch) | |
| tree | 1eb84c955dc96f4c6bcb75b36f6113ccad8e2f1e /src/pardes.zig | |
| parent | 475cc49ccbe9e81f8ec8aa2371c20fc632d110b9 (diff) | |
| download | pardes-09c35b22793153d201fa202b52671a6442e5e2e1.tar.gz pardes-09c35b22793153d201fa202b52671a6442e5e2e1.zip | |
a builtin to put the pane taglines at the bottom
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 199 |
1 files changed, 144 insertions, 55 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index aa753277..ba80ee22 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -3943,9 +3943,15 @@ fn clampBorderCol(lx: u16, lw: u16, rw: u16, mcol: u16) u16 { /// Where a horizontal border drag settles: `mrow` clamped so either pane may /// shrink to just its tag row (BOX_H) but no further. `ay`/`ah` are the upper /// pane's y and height, `bh` the lower pane's. -fn clampBorderRow(ay: u16, ah: u16, bh: u16, mrow: u16) u16 { - const lo = ay + BOX_H - 1; - const hi = ay +| ah +| bh -| (BOX_H + 1); +/// +/// The handle is the seam row that is a BODY row, which is the upper pane's +/// LAST row normally and — with Tagbottom, where that row is the upper pane's +/// tag — the lower pane's FIRST. That is the same seam one row further down, +/// so both walls simply move with it. +fn clampBorderRow(ay: u16, ah: u16, bh: u16, mrow: u16, tag_bottom: bool) u16 { + const d: u16 = if (tag_bottom) 1 else 0; + const lo = ay + BOX_H - 1 + d; + const hi = (ay +| ah +| bh -| (BOX_H + 1)) +| d; return if (lo <= hi) std.math.clamp(mrow, lo, hi) else mrow; } @@ -3967,25 +3973,32 @@ test "a corner drag's two axes clamp independently" { // each axis alone, at both walls try std.testing.expectEqual(@as(u16, config.MINW), clampBorderCol(lx, lw, rw, 0)); try std.testing.expectEqual(@as(u16, 100 - config.MINW), clampBorderCol(lx, lw, rw, 999)); - try std.testing.expectEqual(row_lo, clampBorderRow(ay, ah, bh, 0)); - try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999)); + try std.testing.expectEqual(row_lo, clampBorderRow(ay, ah, bh, 0, false)); + try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999, false)); // THE corner property: drag off the right edge while staying mid-height — // x sits at its wall, y still tracks the mouse row exactly try std.testing.expectEqual(@as(u16, 100 - config.MINW), clampBorderCol(lx, lw, rw, 999)); - try std.testing.expectEqual(@as(u16, 12), clampBorderRow(ay, ah, bh, 12)); + try std.testing.expectEqual(@as(u16, 12), clampBorderRow(ay, ah, bh, 12, false)); // and the mirror: pinned at the bottom, x still free - try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999)); + try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999, false)); try std.testing.expectEqual(@as(u16, 40), clampBorderCol(lx, lw, rw, 40)); // both pinned at once is just both walls, not a stuck gesture try std.testing.expectEqual(@as(u16, config.MINW), clampBorderCol(lx, lw, rw, 0)); - try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999)); + try std.testing.expectEqual(row_hi, clampBorderRow(ay, ah, bh, 999, false)); + + // Tagbottom moves the handle to the LOWER pane's first row, one further + // down, and both walls travel with it — either pane still bottoms out at + // its bare tag row and neither can be squeezed away + try std.testing.expectEqual(row_lo + 1, clampBorderRow(ay, ah, bh, 0, true)); + try std.testing.expectEqual(row_hi + 1, clampBorderRow(ay, ah, bh, 999, true)); + try std.testing.expectEqual(@as(u16, 12), clampBorderRow(ay, ah, bh, 12, true)); // degenerate pairs pass through untouched, each on its own terms: a pane // pair with no room stays draggable even when the column pair has room try std.testing.expectEqual(@as(u16, 7), clampBorderCol(0, 5, 5, 7)); try std.testing.expectEqual(@as(u16, 40), clampBorderCol(lx, lw, rw, 40)); - try std.testing.expectEqual(@as(u16, 9), clampBorderRow(TOPBAR_H, 1, 0, 9)); + try std.testing.expectEqual(@as(u16, 9), clampBorderRow(TOPBAR_H, 1, 0, 9, false)); } pub const Rect = struct { x: u16, y: u16, w: u16, h: u16 }; @@ -4104,6 +4117,12 @@ pub const Pardes = struct { /// something. Turned off, every row math in here is the one-row-per-line /// arithmetic it always was and hscroll comes back. wrap_on: bool = true, + /// every pane's tagline on its LAST row instead of its first (the + /// Tagbottom builtin). Render and hit-testing derive two locals from it — + /// the tag row and the first body row — and nothing else in here knows; + /// the Sel coordinate space is untouched, so row 0 is still the tag and + /// the body still starts at BOX_H whichever end they are drawn at. + tag_bottom: bool = false, show_debug: bool = false, /// the Crt builtin's CRT post-process, rendered by the gui shell only /// (the tty and web shells never read it) @@ -8994,13 +9013,23 @@ pub const Pardes = struct { // pair's h-handle, and the box must stay grabbable to move if (hovered) |mid| { const mr = p.rects[mid]; - if (mcol < mr.x + config.GUTTER and mrow < mr.y + BOX_H) { + const mtag = if (p.tag_bottom) mr.y + mr.h -| BOX_H else mr.y; + if (mcol < mr.x + config.GUTTER and mrow >= mtag and mrow < mtag + BOX_H) { p.drag = .{ .move = .{ .id = mid, .cur_x = mcol, .cur_y = mrow } }; return; } } // resize handles next: a pane's own trailing edge (v: the - // left column's last col; h: the upper pane's last row). + // left column's last col; h: the seam row between a stacked + // pair that is a BODY row — the upper pane's last, or with + // Tagbottom, where that one is the upper pane's tag, the + // lower pane's first). + // Either way the handle EATS one body row of one of the two + // panes — a press there resizes instead of placing a cursor + // — and it always has. Tagbottom does not change how many + // rows that costs, only WHICH pane pays: the lower pane's + // first row rather than the upper pane's last. A no-drag + // click on it is a no-op in both orientations. // The v test still wins outright, but it now also asks // whether this same cell is one of ITS OWN column's h // handles — that cell is the corner where the two lines @@ -9011,7 +9040,8 @@ pub const Pardes = struct { var corner: ?usize = null; for (0..p.col_n[c] -| 1) |k| { const r = p.rects[p.col_terms[c][k]]; - if (mrow == r.y + r.h -| 1) { + const seam = if (p.tag_bottom) r.y +| r.h else r.y + r.h -| 1; + if (mrow == seam) { corner = k; break; } @@ -9024,7 +9054,8 @@ pub const Pardes = struct { if (mcol < p.col_x[cc] or mcol >= p.col_x[cc] + p.col_w[cc]) continue; for (0..p.col_n[cc] -| 1) |k| { const r = p.rects[p.col_terms[cc][k]]; - if (mrow == r.y + r.h -| 1) { + const seam = if (p.tag_bottom) r.y +| r.h else r.y + r.h -| 1; + if (mrow == seam) { p.drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = mrow } }; return; } @@ -9033,11 +9064,18 @@ pub const Pardes = struct { const id = hovered orelse return; const r = p.rects[id]; const pane = p.panes[id] orelse return; + // the same two rows renderPane painted through (see there): + // the tag's, and the body's first. A Sel row is still 0 for + // the tag and BOX_H upward for the body whichever end they + // are at, so the mapping happens HERE and everything + // downstream of a Sel is untouched. + const tag_y = if (p.tag_bottom) r.y + r.h -| BOX_H else r.y; + const body_y = if (p.tag_bottom) r.y else r.y + BOX_H; if (mcol < r.x + config.GUTTER) { // gutter scrollbar: a left click scrolls UP to that row p.active = id; - pane.scrollBy(-(@as(i32, mrow) - @as(i32, r.y + BOX_H))); - } else if (mrow < r.y + BOX_H) { + pane.scrollBy(-(@as(i32, mrow) - @as(i32, body_y))); + } else if (mrow >= tag_y and mrow < tag_y + BOX_H) { // left press on the tag row: focus the tag AT that // column (a rendered-tag column — the mouse and the tag // agree, no clamp into the tail) and anchor a sweep, so @@ -9050,7 +9088,7 @@ pub const Pardes = struct { p.active = id; exitTagEdit(pane); // clicking the body leaves tag editing const sc: i32 = @as(i32, mcol) - @as(i32, r.x + config.GUTTER); - const v: i32 = @as(i32, mrow) - @as(i32, r.y); + const v: i32 = @as(i32, mrow) - @as(i32, body_y) + @as(i32, BOX_H); pane.sel[sel_slot] = .{ .state = .dragging, .c0 = sc, .c1 = sc, .r0 = v, .r1 = v }; const pdf_native = if (comptime pdf_enabled) p.pdfNativeReady(pane) else false; if (pdf_native) _ = p.beginPdfDrag(pane, mcol, mrow, false); @@ -9111,17 +9149,27 @@ pub const Pardes = struct { const id = hovered orelse return; const r = p.rects[id]; const pane = p.panes[id] orelse return; - if (mcol < r.x + config.GUTTER and mrow >= r.y + BOX_H) { + // the tag row and the body's first, exactly as the select + // arm above derives them + const tag_y = if (p.tag_bottom) r.y + r.h -| BOX_H else r.y; + const body_y = if (p.tag_bottom) r.y else r.y + BOX_H; + const on_tag = mrow >= tag_y and mrow < tag_y + BOX_H; + if (mcol < r.x + config.GUTTER and !on_tag) { // gutter: right scrolls DOWN to here, mirroring left's up; // middle matches left. Right focuses (look lands you // there); middle does not. - const local = @as(i32, mrow) - @as(i32, r.y + BOX_H); + const local = @as(i32, mrow) - @as(i32, body_y); if (m.button == config.look_button) p.active = id; pane.scrollBy(if (m.button == config.look_button) local else -local); } else if (mcol >= r.x + config.GUTTER) { if (m.button == config.look_button) p.active = id; const c: i32 = @as(i32, mcol) - @as(i32, r.x + config.GUTTER); - const v: i32 = @as(i32, mrow) - @as(i32, r.y); + // unlike the select button, execute and look sweep the + // TAG through this same Sel — that is how middle-clicking + // Del on a tagline runs it — so the tag row is row 0 and + // everything else counts from BOX_H, wherever the tag is + // physically drawn. + const v: i32 = if (on_tag) 0 else @as(i32, mrow) - @as(i32, body_y) + @as(i32, BOX_H); pane.sel[@intFromEnum(m.button)] = .{ .state = .dragging, .c0 = c, .c1 = c, .r0 = v, .r1 = v }; // Once a body gesture starts over a native PDF, keep // it native even when the point misses a letterbox or @@ -9129,7 +9177,7 @@ pub const Pardes = struct { // inert instead of executing hidden projected text. // Tag clicks remain ordinary text/builtin gestures. const pdf_native = if (comptime pdf_enabled) - mrow >= r.y + BOX_H and p.pdfNativeReady(pane) + !on_tag and p.pdfNativeReady(pane) else false; if (pdf_native) _ = p.beginPdfDrag(pane, mcol, mrow, true); @@ -9163,7 +9211,7 @@ pub const Pardes = struct { if (d.corner_idx) |k| if (k + 1 < p.col_n[c]) { const a = p.rects[p.col_terms[c][k]]; const b = p.rects[p.col_terms[c][k + 1]]; - d.cur_y = clampBorderRow(a.y, a.h, b.h, mrow); + d.cur_y = clampBorderRow(a.y, a.h, b.h, mrow, p.tag_bottom); } else { d.cur_y = mrow; }; @@ -9174,7 +9222,7 @@ pub const Pardes = struct { if (k + 1 < p.col_n[cc]) { const a = p.rects[p.col_terms[cc][k]]; const b = p.rects[p.col_terms[cc][k + 1]]; - d.cur_y = clampBorderRow(a.y, a.h, b.h, mrow); + d.cur_y = clampBorderRow(a.y, a.h, b.h, mrow, p.tag_bottom); } else d.cur_y = mrow; }, .move => |*d| { @@ -9186,7 +9234,29 @@ pub const Pardes = struct { const r = p.rects[s.id]; const b = @intFromEnum(s.button); pane.sel[b].c1 = @as(i32, mcol) - @as(i32, r.x + config.GUTTER); - pane.sel[b].r1 = @as(i32, mrow) - @as(i32, r.y); + // A Tagbottom drag STAYS IN THE REGION ITS ANCHOR STARTED + // IN, which is the one place the two coordinate spaces + // genuinely disagree: Sel row 0 is the tag and the body + // counts up from BOX_H, but on screen the tag is now BELOW + // the body. Sweeping down past the last line onto the + // tagline — the ordinary "select to the end" gesture — would + // therefore drive r1 to the TOP of Sel space and invert the + // selection: the highlight jumps above the anchor, and a 1-2 + // Cut on it deletes lines nobody swept. So a body-anchored + // drag is clamped to the body rows and a tag-anchored one + // (execute/look only; the left button's tag sweep is its own + // .tag drag) stays on the tag, which is one line anyway. + // The cost is that a body sweep can no longer be extended + // onto the bottom tagline to pick up the tag text. + // With the tag on TOP, Sel order IS screen order and the + // gesture is contiguous and correct, so nothing is clamped. + if (p.tag_bottom) { + const body_h = r.h -| BOX_H; + pane.sel[b].r1 = if (pane.sel[b].r0 < BOX_H or body_h == 0) + 0 + else + @as(i32, @min(@max(mrow, r.y), r.y + body_h - 1)) - @as(i32, r.y) + @as(i32, BOX_H); + } else pane.sel[b].r1 = @as(i32, mrow) - @as(i32, r.y); if (comptime pdf_enabled) { if (s.pdf.native) { if (p.updatePdfSelection(pane, mcol, mrow, false)) @@ -9406,7 +9476,11 @@ pub const Pardes = struct { const ra = p.rects[p.col_terms[cc][k]]; const rb = p.rects[p.col_terms[cc][k + 1]]; const combined: f32 = @floatFromInt(ra.h + rb.h); - var nt: f32 = @floatFromInt((cur_y + 1) -| ra.y); + // the handle is the upper pane's LAST row (inclusive, hence the +1), or + // with Tagbottom the lower pane's FIRST — which is that row's height + // already, with nothing to add. Either way a no-drag click hands back + // exactly ra.h and nothing moves. + var nt: f32 = @floatFromInt(if (p.tag_bottom) cur_y -| ra.y else (cur_y + 1) -| ra.y); nt = std.math.clamp(nt, @as(f32, BOX_H), @max(@as(f32, BOX_H), combined - BOX_H)); const pair = a.vweight + b.vweight; a.vweight = pair * (nt / combined); @@ -10494,9 +10568,10 @@ pub const Pardes = struct { const r = for (p.panes, 0..) |slot, i| { if (slot == pane) break p.rects[i]; } else return null; - if (mrow < r.y + BOX_H or mrow >= r.y + r.h) return null; + const body_y = if (p.tag_bottom) r.y else r.y + BOX_H; + if (mrow < body_y or mrow >= body_y + (r.h -| BOX_H)) return null; const local_y = @as(f64, @floatFromInt( - @as(u32, mrow - (r.y + BOX_H)) * p.cell_pixels.h + p.cell_pixels.h / 2, + @as(u32, mrow - body_y) * p.cell_pixels.h + p.cell_pixels.h / 2, )); const page = pdfPageAtOffset(&pv, pv.document_scroll_y + local_y); const document_y = pv.document_scroll_y + local_y; @@ -10528,7 +10603,7 @@ pub const Pardes = struct { if (slot == pane) break p.rects[i]; } else return null; const body_x = @as(i64, r.x + config.GUTTER); - const body_y = @as(i64, r.y + BOX_H); + const body_y = @as(i64, if (p.tag_bottom) r.y else r.y + BOX_H); var px = (@as(i64, mcol) - body_x) * p.cell_pixels.w + p.cell_pixels.w / 2; var py = (@as(i64, mrow) - body_y) * p.cell_pixels.h + p.cell_pixels.h / 2; if (clamp_to_page) { @@ -12049,7 +12124,10 @@ pub const Pardes = struct { if (text.len == 0 and !leader_here) continue; const tx = r.x + config.GUTTER; const tw = r.w - config.GUTTER; - const row = r.y + r.h - 1; + // ...at the OTHER end from the tagline, whichever end that is: + // Tagbottom moved the tag onto this row, so the twin moves to the + // pane's first. They stay a pair with the body between them. + const row = if (p.tag_bottom) r.y else r.y + r.h - 1; // In the EDITOR's colours, not the tag bar's: this row is the one // place the program talks back to you about the buffer you are in, // and it reads as part of that buffer rather than as another strip @@ -12153,7 +12231,9 @@ pub const Pardes = struct { if (p.hover_col < p.col_x[cc] or p.hover_col >= p.col_x[cc] + p.col_w[cc]) continue; for (0..p.col_n[cc] -| 1) |k| { const r = p.rects[p.col_terms[cc][k]]; - if (p.hover_row == r.y + r.h -| 1) { + // the same seam the h-handle hit test picks (see there) + const seam = if (p.tag_bottom) r.y +| r.h else r.y + r.h -| 1; + if (p.hover_row == seam) { var col = p.col_x[cc]; while (col < p.col_x[cc] + p.col_w[cc]) : (col += 1) s.overlayDash(col, p.hover_row, "╌"); } @@ -12227,6 +12307,12 @@ pub const Pardes = struct { const chrome = p.chromeTheme(); const tx = r.x + config.GUTTER; // text area (tag + body), right of the gutter const tw = r.w - config.GUTTER; + // The pane's two anchor rows, computed once: the tagline's, and the + // body's first. The Tagbottom builtin swaps which end each is at and + // NOTHING else in here reads r.y — that is the whole of the feature on + // the render side. r.h == 0 returned above, so the bottom row exists. + const tag_y = if (p.tag_bottom) r.y + r.h - 1 else r.y; + const body_y = if (p.tag_bottom) r.y else r.y + BOX_H; // the pane's own background, for everything that has to read as "no // chrome here": the body text, and the blank right half of the // scrollbar's second column. `.default` means the host terminal's own @@ -12272,9 +12358,9 @@ pub const Pardes = struct { const box_lum = (@as(u16, box_bg[0]) * 3 + @as(u16, box_bg[1]) * 6 + @as(u16, box_bg[2])) / 10; const box_ink: [3]u8 = if (box_lum > 140) .{ 0x00, 0x00, 0x00 } else .{ 0xff, 0xff, 0xff }; const box_style: CellStyle = .{ .fg = .{ .rgb = box_ink }, .bg = .{ .rgb = box_bg } }; - s.fill(r.x, r.y, config.GUTTER, BOX_H, box_style); + s.fill(r.x, tag_y, config.GUTTER, BOX_H, box_style); const box_mode = if (pane.tag_edit) pane.tag_mode else pane.mode; - s.set(r.x, r.y, switch (box_mode) { + s.set(r.x, tag_y, switch (box_mode) { .normal => config.tag_normal, .insert => config.tag_insert, .tty => config.tag_tty, @@ -12282,7 +12368,7 @@ pub const Pardes = struct { // tag: live prefix (cwd/path) + editable tail, on a dim bar const tag_style: CellStyle = .{ .fg = .{ .rgb = chrome.tag_fg }, .bg = .{ .rgb = chrome.tag_bg } }; - s.fill(tx, r.y, tw, BOX_H, .{ .bg = .{ .rgb = chrome.tag_bg } }); + s.fill(tx, tag_y, tw, BOX_H, .{ .bg = .{ .rgb = chrome.tag_bg } }); const tag = try p.tagText(arena, pane); // An armed input — `/`, Find, Grep, Rename, WsSymbols, Select/Split, // `|` — is still TYPED INTO the tag tail: same buffer, same offsets, @@ -12301,7 +12387,7 @@ pub const Pardes = struct { @min(tag.len, tag.len - @min(tag.len, pane.tag_tail.items.len) + at) else tag.len; - _ = s.print(tx, r.y, tw, tag[0..tag_cut], tag_style); + _ = s.print(tx, tag_y, tw, tag[0..tag_cut], tag_style); // tag char selection highlight (helix v/x, or a tagline sweep), // inclusive [lo, hi] — rendered-tag columns, so no conversion. // @@ -12315,7 +12401,7 @@ pub const Pardes = struct { var col: usize = b.lo; const end: usize = b.hi; while (col <= end and col < tw) : (col += 1) { - const cell = s.at(tx + @as(u16, @intCast(col)), r.y); + const cell = s.at(tx + @as(u16, @intCast(col)), tag_y); cell.default = false; cell.style.bg = .{ .rgb = th.sel_bg }; cell.style.fg = .{ .rgb = th.sel_fg }; @@ -12324,7 +12410,7 @@ pub const Pardes = struct { // cursor while editing the tag: a rendered-tag column, verbatim if (active and pane.tag_edit) { // bar while typing, block for `:` normal mode (same rule as a body) - if (pane.tag_col < tw) s.cursor = .{ .x = tx + pane.tag_col, .y = r.y, .bar = pane.mode == .insert }; + if (pane.tag_col < tw) s.cursor = .{ .x = tx + pane.tag_col, .y = tag_y, .bar = pane.mode == .insert }; } // A native PDF page uses the same backend-neutral pixel attachment as @@ -12332,8 +12418,8 @@ pub const Pardes = struct { // extracted text projection becomes an ordinary readable body. if (comptime pdf_enabled) { if (hasPdf(pane) and p.native_images and p.drawPdf(pane, r, tx, tw)) { - s.fill(r.x, r.y + BOX_H, 1, r.h -| BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); - s.fill(r.x + 1, r.y + BOX_H, 1, r.h -| BOX_H, .{ .bg = pane_bg }); + s.fill(r.x, body_y, 1, r.h -| BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); + s.fill(r.x + 1, body_y, 1, r.h -| BOX_H, .{ .bg = pane_bg }); if (r.h > BOX_H) { const pv = &pane.pdf.?; const track_h: usize = r.h - BOX_H; @@ -12353,7 +12439,7 @@ pub const Pardes = struct { )); var sy = pos; while (sy < track_h and sy < pos + len) : (sy += 1) - s.fill(r.x, r.y + BOX_H + @as(u16, @intCast(sy)), 1, 1, .{ .bg = .{ .rgb = chrome.scroll_thumb } }); + s.fill(r.x, body_y + @as(u16, @intCast(sy)), 1, 1, .{ .bg = .{ .rgb = chrome.scroll_thumb } }); } return; } @@ -12366,8 +12452,8 @@ pub const Pardes = struct { p.drawImage(pane, r, tx, tw); // thumbless, but the same one column as the real scrollbar below — // that is the whole point of drawing it - s.fill(r.x, r.y + BOX_H, 1, r.h -| BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); - s.fill(r.x + 1, r.y + BOX_H, 1, r.h -| BOX_H, .{ .bg = pane_bg }); + s.fill(r.x, body_y, 1, r.h -| BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); + s.fill(r.x + 1, body_y, 1, r.h -| BOX_H, .{ .bg = pane_bg }); return; } @@ -12384,7 +12470,7 @@ pub const Pardes = struct { var i: u16 = 0; while (it.next()) |line| : (i += 1) { if (i >= body_h) break; - _ = s.print(tx, r.y + BOX_H + i, tw, line, body_style); + _ = s.print(tx, body_y + i, tw, line, body_style); } // the two file-only passes and the terminal-only one: each is the @@ -12409,7 +12495,7 @@ pub const Pardes = struct { var wr: u16 = 0; while (wr + 1 < pane.wrap_n and wr + 1 < body_h) : (wr += 1) { if (pane.wrap_line[wr + 1] != pane.wrap_line[wr]) continue; - s.set(tx + tw - 1, r.y + BOX_H + wr, config.wrap_marker, .{ + s.set(tx + tw - 1, body_y + wr, config.wrap_marker, .{ .fg = .{ .rgb = chrome.lineno }, .bg = pane_bg, }); @@ -12454,9 +12540,12 @@ pub const Pardes = struct { var row: u16 = 0; while (row < r.h) : (row += 1) { if (@as(i32, row) < r0 or @as(i32, row) > r1) continue; + // a Sel row, so 0 is the tag and BOX_H upward is the body — + // the one place the two coordinate spaces meet on the render side + const sy = if (row < BOX_H) tag_y else body_y + row - BOX_H; var col: i32 = if (pane.file != null) @max(c0, @as(i32, config.PREFIX_W)) else c0; while (col <= c1 and col < tw) : (col += 1) { - const cell = s.at(tx + @as(u16, @intCast(col)), r.y + row); + const cell = s.at(tx + @as(u16, @intCast(col)), sy); cell.default = false; cell.style.bg = .{ .rgb = sel_btn[b] }; cell.style.fg = .{ .rgb = th.sel_fg }; @@ -12476,7 +12565,7 @@ pub const Pardes = struct { if (ar < lo or ar > hi) continue; var col: u16 = if (pane.file != null) config.PREFIX_W else 0; while (col < tw) : (col += 1) { - const cell = s.at(tx + col, r.y + row); + const cell = s.at(tx + col, body_y + row - BOX_H); cell.default = false; cell.style.bg = .{ .rgb = th.sel_bg }; cell.style.fg = .{ .rgb = th.sel_fg }; @@ -12531,12 +12620,11 @@ pub const Pardes = struct { while (vr + @as(i32, BOX_H) < @as(i32, r.h)) : (vr += 1) { const w = pane.wrapAt(vr); if (w.line < bnd.lo_row or w.line > bnd.hi_row) continue; - const prow = vr + @as(i32, BOX_H); const cstart: i32 = if (w.line == bnd.lo_row) bnd.lo_col - w.at + vpfx else vpfx; const cend: i32 = if (w.line == bnd.hi_row) bnd.hi_col - w.at + vpfx else @as(i32, tw) - 1; var col: i32 = @max(cstart, vpfx); while (col <= cend and col < tw) : (col += 1) { - const cell = s.at(tx + @as(u16, @intCast(col)), r.y + @as(u16, @intCast(prow))); + const cell = s.at(tx + @as(u16, @intCast(col)), body_y + @as(u16, @intCast(vr))); cell.default = false; cell.style.bg = .{ .rgb = bg }; cell.style.fg = .{ .rgb = th.sel_fg }; @@ -12547,7 +12635,7 @@ pub const Pardes = struct { const crow = cw.row + @as(i32, BOX_H); const ccol = sr.col - cw.at + vpfx; if (crow >= BOX_H and crow < @as(i32, r.h) and ccol >= vpfx and ccol < tw) { - const cell = s.at(tx + @as(u16, @intCast(ccol)), r.y + @as(u16, @intCast(crow))); + const cell = s.at(tx + @as(u16, @intCast(ccol)), body_y + @as(u16, @intCast(crow - BOX_H))); cell.default = false; cell.style.bg = .{ .rgb = th.sel_fg }; cell.style.fg = .{ .rgb = th.sel_bg }; @@ -12574,9 +12662,9 @@ pub const Pardes = struct { else ccol; if (prow >= BOX_H and cx >= 0 and prow < r.h and cx < tw) - s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y + @as(u16, @intCast(prow)), .bar = pane.mode == .insert }; + s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = body_y + @as(u16, @intCast(prow - BOX_H)), .bar = pane.mode == .insert }; } else if (cur.y + BOX_H < r.h and cur.x < tw) { - s.cursor = .{ .x = tx + cur.x, .y = r.y + cur.y + BOX_H, .bar = pane.mode == .insert }; + s.cursor = .{ .x = tx + cur.x, .y = body_y + cur.y, .bar = pane.mode == .insert }; } } @@ -12600,8 +12688,8 @@ pub const Pardes = struct { // config.GUTTER columns and the click handlers still scroll on any of // them, so the blank column is still live. Only the ink narrowed. if (r.h > BOX_H) { - s.fill(r.x, r.y + BOX_H, 1, r.h - BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); - s.fill(r.x + 1, r.y + BOX_H, 1, r.h - BOX_H, .{ .bg = pane_bg }); + s.fill(r.x, body_y, 1, r.h - BOX_H, .{ .bg = .{ .rgb = chrome.scroll_track } }); + s.fill(r.x + 1, body_y, 1, r.h - BOX_H, .{ .bg = pane_bg }); const sb: struct { total: usize, offset: usize, len: usize } = if (pane.file) |*f| .{ .total = file_pane.nlines(p.gpa, f), .offset = f.scroll, @@ -12620,7 +12708,7 @@ pub const Pardes = struct { const pos = (track_h * sb.offset) / total; var sy: usize = pos; while (sy < track_h and sy < pos + len) : (sy += 1) { - s.fill(r.x, r.y + BOX_H + @as(u16, @intCast(sy)), 1, 1, .{ .bg = .{ .rgb = chrome.scroll_thumb } }); + s.fill(r.x, body_y + @as(u16, @intCast(sy)), 1, 1, .{ .bg = .{ .rgb = chrome.scroll_thumb } }); } } } @@ -12757,7 +12845,7 @@ pub const Pardes = struct { .pixel_offset_y = placed.pixel_offset_y, }, .x = tx, - .y = r.y + BOX_H, + .y = if (p.tag_bottom) r.y else r.y + BOX_H, .w = tw, .h = r.h - BOX_H, .rgba = slot.rgba, @@ -12787,6 +12875,7 @@ pub const Pardes = struct { if (iv.rgba.len == 0 or r.h <= BOX_H) return; const body_cols = tw; const body_rows = r.h - BOX_H; + const body_y = if (p.tag_bottom) r.y else r.y + BOX_H; const use_petscii = iv.petscii or !p.native_images; if (!use_petscii) { if (!p.appendImagePlace(.{ @@ -12795,7 +12884,7 @@ pub const Pardes = struct { } else 0), .serial = pane.serial, .x = tx, - .y = r.y + BOX_H, + .y = body_y, .w = body_cols, .h = body_rows, .rgba = iv.rgba, @@ -12839,7 +12928,7 @@ pub const Pardes = struct { .terminal => .{ .index = c.bg }, }; const sx = tx + @as(u16, @intCast(offx + cx)); - const sy = r.y + BOX_H + @as(u16, @intCast(offy + cy)); + const sy = body_y + @as(u16, @intCast(offy + cy)); if (sx < s.cols and sy < s.rows) s.set(sx, sy, c.glyph[0..c.glen], .{ .fg = fg, .bg = bg }); } |
