From 766ed915b18dde41765580f3b7d41d36e004d282 Mon Sep 17 00:00:00 2001 From: foxnne Date: Mon, 19 Oct 2026 15:31:42 -0500 Subject: [PATCH] workbench: explorer icons read on a float's pane and every material A file-tree row's glyph away from the pointer rested in the theme's window fill (Fizzy Dark 28,29,36), lerped toward its accent as the pointer nears. That reads only on the main window's own base. A popped-out float puts the explorer on its pane's card (38,39,45 measured on Liquid Glass), a few steps from the window fill, so the icons were the colour under them. A file's glyph now rests in one colour wherever it is shown, `core.widgets.treeGlyphColor`: the theme's control text, as the tree's labels, a document tab's icon and a row carried in glass already were. Co-Authored-By: Claude Opus 5.5 --- app/layout/ViewDrag.zig | 2 +- core/widgets.zig | 10 +++++++++ plugins/workbench/src/Workspace.zig | 2 +- plugins/workbench/src/files.zig | 2 +- tests/integration.zig | 33 +++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/app/layout/ViewDrag.zig b/app/layout/ViewDrag.zig index 664e544f1..1584869d0 100644 --- a/app/layout/ViewDrag.zig +++ b/app/layout/ViewDrag.zig @@ -2063,7 +2063,7 @@ fn drawTabFace(l: *Layout, d: ViewDrag, title: []const u8) void { // selected tab's — it is the tab in hand. The face drawn before the tab became glass is the // strip's own, so anything else here changed on the way back. const theme = dvui.themeGet(); - const icon_color = theme.color(.control, .text); + const icon_color = core.widgets.treeGlyphColor(theme); const color = theme.color(.window, .text); if (asRows(d)) return drawRows(l, &d, icon_color, color); var row = dvui.box(@src(), .{ .dir = .horizontal }, .{ .gravity_x = 0.5, .gravity_y = 0.5 }); diff --git a/core/widgets.zig b/core/widgets.zig index 7d7082579..52b4f6430 100644 --- a/core/widgets.zig +++ b/core/widgets.zig @@ -319,6 +319,16 @@ pub fn treeRowGlyphSize() dvui.Size { return .{ .w = h, .h = h }; } +/// The colour a file's glyph rests in — a file tree's rows (lit toward their accent as the pointer +/// nears), a document tab's, a row or tab carried in glass — so a file looks the same wherever it +/// is shown: the theme's control text, as the tree's labels. Not the window's fill, the theme's +/// darkest, which the tree's glyphs once rested in: that read only on the main window's own base, +/// and on a pane's card (a popped-out float's, under any window material) it was the colour +/// under them. +pub fn treeGlyphColor(theme: dvui.Theme) dvui.Color { + return theme.color(.control, .text); +} + /// A tree row's expand caret, in its own glyph slot: down when `expanded`, right when not. /// /// The two arrows are different shapes (the down one wide, the right one tall) and the icon fits diff --git a/plugins/workbench/src/Workspace.zig b/plugins/workbench/src/Workspace.zig index 254dc20f6..ca45168c7 100644 --- a/plugins/workbench/src/Workspace.zig +++ b/plugins/workbench/src/Workspace.zig @@ -329,7 +329,7 @@ fn drawTabs(self: *Workspace, region: sdk.Host.Region, tabs: []const *sdk.Surfac // Same fixed glyph slot as the file tree. const tab_doc_path = if (doc_opt) |doc| doc.owner.documentPath(doc) else loading.?.path; - const tab_icon_color = dvui.themeGet().color(.control, .text); + const tab_icon_color = core.widgets.treeGlyphColor(dvui.themeGet()); { var icon_slot = core.widgets.treeRowGlyph(@src(), .{ .gravity_y = 0.5, .margin = .{ .x = 4, .w = 2 } }); defer icon_slot.deinit(); diff --git a/plugins/workbench/src/files.zig b/plugins/workbench/src/files.zig index df4ee2784..ed5f71fd2 100644 --- a/plugins/workbench/src/files.zig +++ b/plugins/workbench/src/files.zig @@ -959,7 +959,7 @@ pub fn recurseFiles(root_directory: []const u8, root_label: []const u8, outer_tr const t = 1.0 - (distance / max_distance); - color = dvui.themeGet().color(.window, .fill).lerp(color, t); + color = core.widgets.treeGlyphColor(dvui.themeGet()).lerp(color, t); if (branch.floating()) { if (dvui.dataGetSlice(null, inner_unique_id, "removed_path", []u8) == null) diff --git a/tests/integration.zig b/tests/integration.zig index bb08a5378..8d7522537 100644 --- a/tests/integration.zig +++ b/tests/integration.zig @@ -6991,6 +6991,39 @@ test "workbench: where a tab carried over a strip goes in is read as if its open try std.testing.expectEqual(@as(usize, 3), pane.insertIndexAt(500, 3)); } +fn glyphLuma(c: dvui.Color) f32 { + return 0.2126 * @as(f32, @floatFromInt(c.r)) + 0.7152 * @as(f32, @floatFromInt(c.g)) + 0.0722 * @as(f32, @floatFromInt(c.b)); +} + +test "workbench: a file tree's glyphs stand off the main window's base and a float's pane alike" { + // Fizzy Dark's colours, as `Editor` sets them up (no themes are loaded here). + var fizzy_dark = dvui.Theme.builtin.adwaita_dark; + fizzy_dark.window = .{ .fill = .{ .r = 28, .g = 29, .b = 36 }, .text = .{ .r = 206, .g = 163, .b = 127 } }; + fizzy_dark.control = .{ .fill = .{ .r = 28, .g = 29, .b = 36 }, .text = .{ .r = 134, .g = 138, .b = 148 } }; + fizzy_dark.fill = .{ .r = 42, .g = 44, .b = 54 }; + // The report, measured in a popped-out float on Liquid Glass: its explorer's pane card, and the + // main window's sidebar beside it. The window's fill, which the glyphs rested in, was the + // sidebar's dark glyph and within a few steps of the float's card: invisible there. + const float_card: dvui.Color = .{ .r = 38, .g = 39, .b = 45 }; + const main_base: dvui.Color = .{ .r = 65, .g = 67, .b = 74 }; + try std.testing.expect(@abs(glyphLuma(fizzy_dark.color(.window, .fill)) - glyphLuma(float_card)) < 15); + const glyph = fizzy.core.widgets.treeGlyphColor(fizzy_dark); + try std.testing.expect(@abs(glyphLuma(glyph) - glyphLuma(float_card)) > 60); + try std.testing.expect(@abs(glyphLuma(glyph) - glyphLuma(main_base)) > 40); + + // And in every theme fizzy ships with dvui's: off the window's fill and the content fill (a + // pane's card), whichever a tree is drawn on, in the main window or a float. + for (dvui.Theme.builtins) |theme| { + const g = fizzy.core.widgets.treeGlyphColor(theme); + for ([_]dvui.Color{ theme.color(.window, .fill), theme.color(.content, .fill) }) |base| { + std.testing.expect(@abs(glyphLuma(g) - glyphLuma(base)) > 40) catch |err| { + std.debug.print("{s}: glyph {f} on {f}\n", .{ theme.name, g, base }); + return err; + }; + } + } +} + // An app's home page in place of the workbench's: a surface keyworded `home`, shown in the `Home` // place the sole document pane declares while it is empty (`Workspace.drawHome`). The pane here is // declared as `Workspace.draw` declares it, through the plugin's `Host`, so the place is the one