diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 15:43:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | cd6ce6c2b3015c776644fb3866280f5dfd03addc (patch) | |
| tree | d4e38e5375b71ebec21ee18fe44870e314b2c783 /src | |
| parent | 0465775799cff895e3e830d2aa19f57966cdf21c (diff) | |
| download | pardes-cd6ce6c2b3015c776644fb3866280f5dfd03addc.tar.gz pardes-cd6ce6c2b3015c776644fb3866280f5dfd03addc.zip | |
A notice's word under the pointer is lit, in its band and the grid; mark_hover is gone
Open point 6 of docs/render-pipeline.md: notice words are Look and Exec
targets, so the hover affordance headers have is back on notices, painted
once in the notice's layer and joined into the grid from it. Deleted:
Surface.mark_hover, Cell.hover and render's per-frame reset of it, none of
which had been set since stage 5 (macOS loses its glass hover rect; its
build is ignored for now). Snapshot goldens unchanged.
Shared files touched: macos.zig (the hover bit's removal). Not touched:
pardes.zig, Messages.zig, mouse.zig, gui.zig, detached/*.
Diffstat (limited to 'src')
| -rw-r--r-- | src/body_layer.zig | 4 | ||||
| -rw-r--r-- | src/draw.zig | 47 | ||||
| -rw-r--r-- | src/macos.zig | 18 | ||||
| -rw-r--r-- | src/surface.zig | 16 |
4 files changed, 46 insertions, 39 deletions
diff --git a/src/body_layer.zig b/src/body_layer.zig index 583a2e98..e9407719 100644 --- a/src/body_layer.zig +++ b/src/body_layer.zig @@ -294,9 +294,7 @@ pub fn renderBody(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane 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 - // win over this quiet affordance. mark_hover carries the - // cells this paints out to the hosts as the affordance rect — - // macOS composes its glass overlay over exactly these. + // win over this quiet affordance. const preview_bg = colors.mix(page_rgb, colors.mix(page_rgb, th.sel_bg)); if (preview.file_word) |word| panes.File.paintWordSelection(p, s, pane, r, word.row, word.lo, word.hi, preview_bg) diff --git a/src/draw.zig b/src/draw.zig index 4242a6e7..3798bf59 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -163,10 +163,6 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { // the pointer per-text (the macOS I-beam) read this as "target" and // afford it the arrow instead. if (p.look_hover_preview != null) s.pointer_shape = .target; - // Stale hover bits must not survive into a frame whose pointer has - // moved on: the ordinary content paint does not clear fields it - // never set. - for (s.cells) |*c| c.hover = false; for (&s.body_layers) |*layer| layer.rows = 0; for (&s.tag_layers) |*layer| layer.rows = 0; s.nimages = 0; @@ -661,10 +657,11 @@ fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, layer: *pardes.Layer, var surface: Surface = .{ .cols = columns, .rows = 1, .cells = layer.cells[index * columns ..][0..columns] }; surface.fill(0, 0, columns, 1, .{ .bg = .{ .rgb = bg }, .font_role = .tagline }); _ = surface.print(0, 0, columns, shown[panes.File.rawAtDisplay(shown, scroll)..], .{ .fg = .{ .rgb = fg }, .bg = .{ .rgb = bg }, .font_role = .tagline }); - // The word under the pointer: on a header even while it is edited, - // but not on a notice (a message is not a command line), nor on a - // column a carried column's pointer is aiming at a place over. - const hovered = p.pointer_inside and kind != .notice and !(kind == .column and p.drag == .column_move); + // The word under the pointer, a Look or Exec target: on a header even + // while it is edited, on a notice, and on the grid's copy of either + // (joined from this); not on a column a carried column's pointer is + // aiming at a place over. + const hovered = p.pointer_inside and !(kind == .column and p.drag == .column_move); if (hovered) { const tag_hit = p.pointer_tag_hit; const col: ?usize = if (tag_hit) |pointer| @@ -1033,6 +1030,40 @@ test "place puts every part where the painters and layers put it, in every layou } } +test "the word under the pointer on a notice is lit in its layer and in the grid's copy" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 41, .rows = 12 }); + defer p.deinit(); + _ = try p.setTestFile("text\n"); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + try std.testing.expect(p.executeBuiltinLine(p.active, "Msg Look here")); + p.sync(); + var s = try p.render(arena.allocator()); + const notice = for (s.regionList()) |region| { + if (region.kind == .notice) break region; + } else return error.TestExpectedNotice; + const row = s.cells[@as(usize, notice.rect.y) * s.cols ..][0..s.cols]; + // "here", the chip's last word, before its blank edge. + const here = notice.rect.x + notice.rect.w - 5; + try std.testing.expectEqualStrings("h", row[here].grapheme()); + p.pointer_inside = true; + p.hover_col = here + 1; + p.hover_row = notice.rect.y; + _ = arena.reset(.retain_capacity); + s = try p.render(arena.allocator()); + const lit = s.cells[@as(usize, notice.rect.y) * s.cols ..][0..s.cols]; + const sel: Color = .{ .rgb = p.theme().sel_bg }; + for (lit[here..][0..4]) |cell| try std.testing.expectEqual(sel, cell.style.bg); + try std.testing.expect(!std.meta.eql(sel, lit[here - 2].style.bg)); + const layer = for (s.tagLayers()) |*layer| { + if (layer.rows != 0 and layer.kind == .notice) break layer; + } else return error.TestExpectedNotice; + const lit_in_layer = for (layer.cells) |cell| { + if (std.meta.eql(sel, cell.style.bg)) break true; + } else false; + try std.testing.expect(lit_in_layer); +} + test "a wide glyph cut by a tag's or a notice's edge is joined into the grid as blank, never half drawn" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 41, .rows = 12 }); defer p.deinit(); diff --git a/src/macos.zig b/src/macos.zig index f7e03cd4..4d8f2883 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -1866,13 +1866,10 @@ fn blankTagCells(cells: []Cell, cols: u16, rows: u16, source: []const pardes.Cel if (layer.rows == 0 or layer.viewport.y >= rows or layer.viewport.x >= cols) continue; const start = @as(usize, layer.viewport.y) * cols + layer.viewport.x; const count = @min(layer.viewport.w, cols - layer.viewport.x); - for (cells[start..][0..count], source[start..][0..count]) |*cell, was| { - // The tag text is the layer's to draw, but a look-hover on a tag - // word is a canonical fact: carry the hover bit through so the - // glass overlay still finds the affordance rect. - cell.* = encodeCell(.{}); - cell.flags |= @as(u8, @intFromBool(was.hover)) * cell_flag_hover; - } + // The tag text is the layer's to draw. (The look-hover's glass + // rect went with the core's hover bit, stage 8's follow-up.) + _ = source; + for (cells[start..][0..count]) |*cell| cell.* = encodeCell(.{}); } } @@ -2574,7 +2571,7 @@ fn encodeCell(cell: pardes.Cell) Cell { .fg = encodeColor(cell.style.fg), .bg = encodeColor(cell.style.bg), .attrs = encodeAttrs(cell.style), - .flags = encodeCellFlags(cell.default, cell.style.font_role, cell.hover), + .flags = encodeCellFlags(cell.default, cell.style.font_role, false), .len = if (cell.default) 1 else cell.len, }; if (cell.default) @@ -3131,16 +3128,11 @@ test "mac tag layer ABI preserves logical capacity and physical grip" { try std.testing.expectEqual(@as(u32, 11), pardes_tag_layer_value(0, 8)); try std.testing.expectEqual(@as(u8, 'Z'), pardes_tag_layer_cells(0).?[11].text[0]); var canonical: [24]Cell = @splat(encodeCell(.{ .text = .{'G'} ++ .{0} ** 6, .len = 1, .default = false })); - // A look-hover under the layer is a canonical fact and survives the blank; - // the glyph it marks does not, because the layer redraws that text itself. var source: [24]pardes.Cell = @splat(.{}); - source[11].hover = true; blankTagCells(&canonical, 8, 3, &source, &core.surface.tag_layers); try std.testing.expectEqual(@as(u8, 'G'), canonical[9].text[0]); try std.testing.expectEqual(@as(u8, 'G'), canonical[18].text[0]); try std.testing.expectEqual(encodeCell(.{}).len, canonical[10].len); - try std.testing.expectEqual(@as(u8, 0), canonical[10].flags & cell_flag_hover); - try std.testing.expectEqual(cell_flag_hover, canonical[11].flags & cell_flag_hover); } test "mac window blur builtin reaches the ABI without changing opacity" { diff --git a/src/surface.zig b/src/surface.zig index 07b593b1..b6f41853 100644 --- a/src/surface.zig +++ b/src/surface.zig @@ -38,12 +38,6 @@ pub const Cell = struct { len: u8 = 1, style: CellStyle = .{}, default: bool = true, - /// The look-hover affordance is on this cell: the word under the pointer - /// is a real Look or Exec operand, and hosts that draw the quiet tint as - /// more than a flat fill — macOS renders it as a glass overlay — read - /// this instead of guessing from the background colour, which a real - /// selection shares. - hover: bool = false, pub fn grapheme(c: *const Cell) []const u8 { return c.text[0..c.len]; @@ -405,10 +399,6 @@ pub const Chrome = struct { pub const Surface = struct { pub const PointerShape = enum(u8) { arrow, link, target }; pointer_shape: PointerShape = .arrow, - /// Set only for the duration of the look-hover paint, so the cells that - /// paint the quiet affordance can carry the hover bit through the ABI - /// without every selection painter learning a new parameter. - mark_hover: bool = false, body_layers: [MAX_PANES]Layer = @splat(.{}), tag_layers: [MAX_TAG_LAYERS]Layer = @splat(.{}), cols: u16 = 0, @@ -474,11 +464,7 @@ pub const Surface = struct { pub fn at(s: *Surface, x: u16, y: u16) *Cell { std.debug.assert(x < s.cols and y < s.rows); - const cell = &s.cells[@as(usize, y) * s.cols + x]; - // The look-hover paint runs with mark_hover set, and its cells are - // exactly the ones this marks — see render's preview block. - if (s.mark_hover) cell.hover = true; - return cell; + return &s.cells[@as(usize, y) * s.cols + x]; } pub fn set(s: *Surface, x: u16, y: u16, text: []const u8, style: CellStyle) void { |
