summaryrefslogtreecommitdiff
path: root/src/panes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-22 09:31:11 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commita1d5ee19a648abc65b557dffa14a3b2f70577286 (patch)
treeef36c11168bcc2f7bbb9b7a6a29519c7e0882fab /src/panes.zig
parent43b9d696f9e25ad7589527b91a74666e71ff0472 (diff)
downloadpardes-a1d5ee19a648abc65b557dffa14a3b2f70577286.tar.gz
pardes-a1d5ee19a648abc65b557dffa14a3b2f70577286.zip
Notices become chips that float over the body, not rows taken from it
A notice is an overlay now, which is the one place it is NOT like the tree-sitter context: it takes no row from the body and moves no text. Each one is a chip as wide as its own message plus a blank cell either side, anchored to the pane right edge on the body top rows. The rest of each of those rows is ordinary body text that still reads and still answers a click -- Notices.left records where each chip starts, and the hit test refuses only the cells it covers rather than the whole row. The width is counted in GRID columns rather than scaled into the tagline face the chip is drawn in, because the canonical grid is what a terminal client draws and a chip narrower than its own text there would clip it; the narrower tagline face simply leaves a little more room inside the chip. The tag layer takes the geometry the grid pass already chose, so the two cannot disagree and the GUI skipping the cells a tag layer covers leaves no stub behind. Pane.body_offset is gone with the reservation it existed for, and bodyTop takes only a rect. The body layer keeps every row it had, the scrollbar runs the full body again, and the GUI smooth-scroll extents go back to the pane. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'src/panes.zig')
-rw-r--r--src/panes.zig46
1 files changed, 26 insertions, 20 deletions
diff --git a/src/panes.zig b/src/panes.zig
index 4b5c6721..f995986b 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -191,11 +191,6 @@ pub const Pane = struct {
/// body layer reserves exactly `len` rows for them, the way it already
/// reserves rows for sticky context headers.
notices: Notices = .{},
- /// Rows the notice bands take off the TOP of the body, the way tree-sitter
- /// context rows do: the body starts below them rather than being painted
- /// under them. Zero while the body LAYER renders, which paints into a
- /// surface of its own that carries no bands.
- body_offset: u16 = 0,
pub const Notices = struct {
/// Painted bottom-up in this order, so a prompt keeps the row nearest
@@ -204,14 +199,25 @@ pub const Pane = struct {
pub const max = @typeInfo(Kind).@"enum".fields.len;
kinds: [max]Kind = undefined,
+ /// Where each band's chip starts, as an absolute grid column. A chip
+ /// is only as wide as its own text, so the rest of the row underneath
+ /// is ordinary body text and a click there has to reach it.
+ left: [max]u16 = @splat(std.math.maxInt(u16)),
len: u8 = 0,
pub fn push(n: *Notices, kind: Kind) void {
if (n.len >= max) return;
n.kinds[n.len] = kind;
+ n.left[n.len] = std.math.maxInt(u16);
n.len += 1;
}
+ /// Is this cell inside the chip drawn for band `i`? Nothing else on
+ /// that row belongs to the notice.
+ pub fn covers(n: *const Notices, i: usize, col: u16) bool {
+ return i < n.len and col >= n.left[i];
+ }
+
pub fn slice(n: *const Notices) []const Kind {
return n.kinds[0..n.len];
}
@@ -2041,7 +2047,7 @@ pub const File = struct {
const gcrow = if (pane.cur_pinned) pane.cur_row else @as(i32, gcur.y) + goff;
const cur_line: i32 = if (active and !pane.tag_edit) gcrow else std.math.minInt(i32);
// the body's first row, the way renderPane derives it (Tagbottom)
- const body_y = p.bodyTop(pane, r);
+ const body_y = p.bodyTop(r);
const context_bg = p.theme().tag_bg;
for (0..@min(pane.context_rows, body_h)) |context_row| {
for (0..tw) |col| {
@@ -2095,7 +2101,7 @@ pub const File = struct {
// indexed start, same as bodyText — an empty tail simply paints nothing
var flines = std.mem.splitScalar(u8, f.content[lineStart(p.gpa, f, f.scroll)..], '\n');
const total = nlines(p.gpa, f);
- const body_y = p.bodyTop(pane, r);
+ const body_y = p.bodyTop(r);
var vr: u16 = 0;
while (vr < body_h) : (vr += 1) {
var base: usize = undefined;
@@ -2171,7 +2177,7 @@ pub const File = struct {
pane_bg: pardes.Color,
) void {
if (tw <= gutterWidth(pane) + 1) return;
- const body_y = p.bodyTop(pane, r);
+ const body_y = p.bodyTop(r);
const marker_fg = p.chromeTheme().lineno;
var row: u16 = 0;
while (row + 1 < pane.wrap_n and row + 1 < body_h) : (row += 1) {
@@ -2195,9 +2201,9 @@ pub const File = struct {
const tx = r.x + config.GUTTER;
const tw = r.w - config.GUTTER;
const prefix_width = gutterWidth(pane);
- const body_y = p.bodyTop(pane, r);
+ const body_y = p.bodyTop(r);
var vr: i32 = 0;
- while (vr + @as(i32, pardes.BOX_H) < @as(i32, r.h -| pane.body_offset)) : (vr += 1) {
+ while (vr + @as(i32, pardes.BOX_H) < @as(i32, r.h)) : (vr += 1) {
const here = pane.wrapAt(vr);
if (here.line != row) continue;
var hi = word_hi;
@@ -5482,7 +5488,7 @@ pub const Pdf = struct {
if (slot == pane) break core.rects[id];
} else return null;
const cols = rect.w -| config.GUTTER;
- const rows = (rect.h -| pardes.BOX_H) -| pane.body_offset;
+ const rows = rect.h -| pardes.BOX_H;
if (cols == 0 or rows == 0) return null;
return .{
.pixel_w = @as(u32, cols) * @as(u32, core.cell_pixels.w),
@@ -5520,8 +5526,8 @@ pub const Pdf = struct {
const rect = for (core.panes, 0..) |slot, id| {
if (slot == pane) break core.rects[id];
} else return null;
- const body_y = core.bodyTop(pane, rect);
- if (row < body_y or row >= body_y + ((rect.h -| pardes.BOX_H) -| pane.body_offset)) return null;
+ const body_y = core.bodyTop(rect);
+ if (row < body_y or row >= body_y + (rect.h -| pardes.BOX_H)) return null;
const local_y = @as(f64, @floatFromInt(
@as(u32, row - body_y) * core.cell_pixels.h + core.cell_pixels.h / 2,
));
@@ -5577,7 +5583,7 @@ pub const Pdf = struct {
if (slot == pane) break core.rects[id];
} else return null;
const body_x = @as(i64, rect.x + config.GUTTER);
- const body_y = @as(i64, core.bodyTop(pane, rect));
+ const body_y = @as(i64, core.bodyTop(rect));
const px = (@as(i64, col) - body_x) * core.cell_pixels.w + core.cell_pixels.w / 2;
const py = (@as(i64, row) - body_y) * core.cell_pixels.h + core.cell_pixels.h / 2;
return pointAtPage(state, view, page, px, py, clamp_to_page);
@@ -5860,7 +5866,7 @@ pub const Pdf = struct {
text_width: u16,
) bool {
if (comptime !enabled) return false;
- if (!core.native_images or rect.h <= pardes.BOX_H + pane.body_offset) return false;
+ if (!core.native_images or rect.h <= pardes.BOX_H) return false;
const state = &(pane.pdf orelse return false);
const view = paneViewport(core, pane) orelse return false;
const key = TintKey{ .mode = state.tint, .colors = tintColors(core) };
@@ -5897,9 +5903,9 @@ pub const Pdf = struct {
null,
},
.x = text_x,
- .y = core.bodyTop(pane, rect),
+ .y = core.bodyTop(rect),
.w = text_width,
- .h = (rect.h -| pardes.BOX_H) -| pane.body_offset,
+ .h = rect.h -| pardes.BOX_H,
.rgba = placed.rgba,
.iw = placed.width,
// The texture contains the retained band, not the full page.
@@ -5911,8 +5917,8 @@ pub const Pdf = struct {
// This frame has spent the motion used to choose its raster band.
state.scroll_travel = 0;
- const body_y = core.bodyTop(pane, rect);
- const body_h = (rect.h -| pardes.BOX_H) -| pane.body_offset;
+ const body_y = core.bodyTop(rect);
+ const body_h = rect.h -| pardes.BOX_H;
const chrome = core.chromeTheme();
const theme = core.theme();
const pane_bg: pardes.Color = if (theme.bg) |color| .{ .rgb = color } else .default;
@@ -7579,7 +7585,7 @@ pub const Terminal = struct {
if (comptime !enabled) return;
const s = &p.surface;
if (pane.terminal == null) return;
- const body_y = p.bodyTop(pane, r);
+ const body_y = p.bodyTop(r);
var filtered_storage: FilteredColors = undefined;
const filtered: ?*FilteredColors = if (pane.tty_filter) blk: {
const tz_filter = tracy.zone(@src(), "filterInit");