diff options
| -rw-r--r-- | docs/render-pipeline.md | 8 | ||||
| -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 |
5 files changed, 51 insertions, 42 deletions
diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md index 6c5e4866..7299b357 100644 --- a/docs/render-pipeline.md +++ b/docs/render-pipeline.md @@ -845,9 +845,11 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach. 6. Revisit at the first visual stage (from stage 5): the notice layer lost its hover word to keep the grid's behaviour. Notice words are Look/Exec targets, so bring the hover affordance back in BOTH the grid and the - layer then. Carried forward from stage 8 (a GUI-only stage) to the hover - change after it, which also retires `mark_hover`: the affordance is a - core paint every shell and snapshot sees. + layer then. Done right after stage 8: a notice's word under the pointer + is lit as a header's is, in the layer and so in the grid's copy joined + from it. The same change deleted `mark_hover`, the `Cell.hover` bit and + the per-frame reset of it (never set since stage 5; macOS loses its + glass hover rect, accepted with open point 4). 7. Revisit at stage 8 (from stage 6): a body without context rows has no layer (paint once, straight onto the grid). If the GUI is to read every body from a layer, give every body one then and measure the copy. The 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 { |
