summaryrefslogtreecommitdiff
path: root/src/body_layer.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/body_layer.zig')
-rw-r--r--src/body_layer.zig79
1 files changed, 59 insertions, 20 deletions
diff --git a/src/body_layer.zig b/src/body_layer.zig
index e1a45058..ac87a07b 100644
--- a/src/body_layer.zig
+++ b/src/body_layer.zig
@@ -257,15 +257,33 @@ pub fn paintPointerSelection(
}
}
-pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize) !void {
- if (!p.settings.tree_context_tag_style or pane.context_rows == 0 or pane.file == null or pane.collapsed) return;
- if (r.w <= config.GUTTER or r.h <= pane.tag_rows) return;
+/// The selection colour of each pointer button's sweep: the select button's
+/// and the look and exec buttons', each tinted toward its own accent.
+pub fn buttonTints(th: *const colors.Theme) [3][3]u8 {
+ const accent2 = if (std.mem.eql(u8, &th.num, &th.str)) th.comment else th.num;
+ return .{
+ colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.kw)),
+ colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.str)),
+ colors.mix(th.sel_bg, colors.mix(th.sel_bg, accent2)),
+ };
+}
+
+/// A pane's body, painted once. A file body with compact tree-sitter
+/// context rows is painted into its body layer -- more rows than the grid
+/// has, the context rows at the tagline's height -- and the grid's rows are
+/// copies of that layer's first ones, at body height. Any other body has no
+/// rows the grid cannot show, and is painted straight onto the grid. The
+/// rail is the grid's alone.
+pub fn joinBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize, active: bool, rail: pardes.Region) !void {
+ const file = if (pane.file) |*f| f else return renderBody(p, s, arena, pane, r, id, active, rail, null);
+ if (!p.settings.tree_context_tag_style or pane.collapsed or r.h <= pane.tag_rows)
+ return renderBody(p, s, arena, pane, r, id, active, rail, null);
+ // Which rows are context rows is decided by the text the body shows.
+ const text = try panes.File.bodyText(arena, pane, file, p.settings.wrap);
const viewport_rows = r.h - pane.tag_rows;
- if (viewport_rows == 0) return;
- // The canonical fixed grid remains available to terminal clients. The
- // separate body layer reserves enough rows for any compact font size.
const logical_rows = @min(@as(usize, pane.rows) + pane.context_rows, pane.wrap_line.len);
- if (logical_rows <= pane.context_rows) return;
+ if (pane.context_rows == 0 or logical_rows <= pane.context_rows)
+ return renderBody(p, s, arena, pane, r, id, active, rail, text);
pane.body_rows = @intCast(logical_rows);
const bh: usize = @max(1, p.row_metrics.body_h);
const th: usize = std.math.clamp(p.row_metrics.tagline_h, 1, bh);
@@ -274,14 +292,14 @@ pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rec
const context_pixels = @as(usize, pane.context_row_limit.?) * th;
pane.body_visible_rows = @intCast(@min(logical_rows, pane.context_row_limit.? + @max(1, (body_pixels -| context_pixels) / bh)));
const width = r.w - config.GUTTER;
- const layer = &p.surface.body_layers[id];
+ const layer = &s.body_layers[id];
const count = logical_rows * width;
if (layer.cells.len != count) layer.cells = try p.gpa.realloc(layer.cells, count);
const temporary_cells = try arena.alloc(Cell, @as(usize, r.w) * (logical_rows + pane.tag_rows));
@memset(temporary_cells, .{});
var temporary: Surface = .{ .cols = r.w, .rows = @intCast(logical_rows + pane.tag_rows), .cells = temporary_cells };
const synthetic: Rect = .{ .x = 0, .y = 0, .w = r.w, .h = @intCast(logical_rows + pane.tag_rows) };
- try renderBody(p, &temporary, arena, pane, synthetic, id, id == p.active, null);
+ try renderBody(p, &temporary, arena, pane, synthetic, id, active, null, null);
const first_row: usize = if (p.settings.tag_bottom) 0 else pane.tag_rows;
for (0..logical_rows) |row| {
const start = (row + first_row) * r.w + config.GUTTER;
@@ -289,7 +307,7 @@ pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rec
}
layer.pane = @intCast(id);
layer.serial = pane.serial;
- const body = p.surface.region(.body, id).?;
+ const body = s.region(.body, id).?;
layer.viewport = .{ .x = body.rect.x, .y = body.rect.y, .w = body.rect.w - body.clip, .h = body.rect.h };
layer.cols = width;
layer.rows = @intCast(logical_rows);
@@ -300,9 +318,29 @@ pub fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rec
if (cursor.x >= config.GUTTER and cursor.y >= first_row)
layer.cursor = .{ .x = cursor.x - config.GUTTER, .y = @intCast(cursor.y - first_row), .bar = cursor.bar };
}
+ // The grid: the layer's first rows, as many as the body has, each a
+ // body row high; its caret where one of them has it. (pane.rows is the
+ // body's height after sync, and context rows are only prepared while
+ // the rows fit the wrap table, so the layer always has these rows.)
+ std.debug.assert(layer.rows >= viewport_rows);
+ for (0..viewport_rows) |row| {
+ const grid_row = s.cells[(body.rect.y + row) * s.cols ..][body.rect.x..][0..width];
+ @memcpy(grid_row, layer.cells[row * width ..][0..width]);
+ }
+ if (layer.cursor) |cursor| if (cursor.y < viewport_rows) {
+ s.cursor = .{ .x = body.rect.x + cursor.x, .y = body.rect.y + cursor.y, .bar = cursor.bar };
+ };
+ const chrome = p.chromeTheme();
+ const theme = p.theme();
+ const pane_bg: Color = if (theme.bg) |c| .{ .rgb = c } else .default;
+ if (rail.rect.h > 0) {
+ s.fill(rail.rect.x, rail.rect.y, 1, rail.rect.h, .{ .bg = .{ .rgb = chrome.scroll_track } });
+ s.fill(rail.rect.x + 1, rail.rect.y, 1, rail.rect.h, .{ .bg = pane_bg });
+ s.fill(rail.rect.x, rail.rect.y + rail.thumb_y, 1, rail.thumb_h, .{ .bg = .{ .rgb = chrome.scroll_thumb } });
+ }
}
-pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize, active: bool, rail: ?pardes.Region) !void {
+pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize, active: bool, rail: ?pardes.Region, built: ?[]const u8) !void {
const th = p.theme();
const chrome = p.chromeTheme();
const tx = r.x + config.GUTTER;
@@ -322,8 +360,14 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane
.bg = pane_bg,
};
const tz_body = tracy.zone(@src(), "bodyText");
- const body = try bodyText(p, arena, pane);
+ const body = built orelse try bodyText(p, arena, pane);
tz_body.end();
+ // A select sweep begun on the tag and dragged into the body marks the
+ // body rows it crossed only where no text is: it is laid down before
+ // the rows, which paint over it.
+ const sweep = pane.sel[pardes.sel_slot];
+ if (sweep.state != .none and (sweep.state != .done or pardes.sel_slot == 0) and @min(sweep.r0, sweep.r1) < pane.tag_rows)
+ paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sweep, pane.tag_rows, th.sel_bg, th.sel_fg);
{
// The rows themselves. `bodyText` above is only the string BUILD;
// this is what writes it into the surface.
@@ -349,12 +393,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane
}
tz_color.end();
- const accent2 = if (std.mem.eql(u8, &th.num, &th.str)) th.comment else th.num;
- const sel_btn = [3][3]u8{
- colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.kw)),
- colors.mix(th.sel_bg, colors.mix(th.sel_bg, th.str)),
- colors.mix(th.sel_bg, colors.mix(th.sel_bg, accent2)),
- };
+ const sel_btn = buttonTints(th);
if (p.look_hover_preview) |preview| {
if (preview.pane == id and preview.serial == pane.serial) {
// Preserve body ink, then let every real user selection below
@@ -365,7 +404,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane
if (preview.file_word) |word|
panes.File.paintWordSelection(p, s, pane, r, word.row, word.lo, word.hi, preview_bg)
else if (preview.sel) |sel|
- paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sel, 0, preview_bg, null);
+ paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sel, pane.tag_rows, preview_bg, null);
}
}
const modal_hover = if (p.look_hover_preview) |preview|
@@ -378,7 +417,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane
// A select sweep over the tag is the tag's selection, painted there
// in the tag's own selection colours.
if (b == pardes.sel_slot and @min(sl.r0, sl.r1) < pane.tag_rows) continue;
- paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, 0, sel_btn[b], th.sel_fg);
+ paintPointerSelection(p, s, pane, r, tx, tw, tag_y, body_y, sl, pane.tag_rows, sel_btn[b], th.sel_fg);
}
// modal line selection (helix `x`): whole body rows, normal mode only
if ((pane.body.mode == .normal or modal_hover) and pane.body.msel.active) {