GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Graph: the grid is relief carved into the plate, not tinted quads
The rails were gap-coloured quads at the network opacity — a second colour
mixed into 40% of the designer's network pane at its rail widths, so no
gap colour could make that pane read as the params plate's material.
paint_grid_relief carves every cell as ONE lattice (the desktop grid's
primitive: mitred crossings, one draw) and engraves the two origin axes as
grooves down the rail centrelines. Shading only: cells and rails are the
plate. GraphController grows the hook, defaulting to nothing, so a host
that draws the graph's quads itself (the designer) carves at the same
point in its own walk. Relief cannot fade, so the network opacity now
fades wires and text only.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/widget/display/graph.rs | 111 +++++++++++++++++++++++---------------------
src/widget/mod.rs | 4 ++
2 files changed, 61 insertions(+), 54 deletions(-)
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 6af3842..d9a4cfb 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -385,6 +385,51 @@ impl Graph {
crate::layout::graph_node_corner_radius().min(cell / 2.0)
}
+ /// The grid as relief in whatever the graph is painted over — the pane
+ /// plate: every cell a well carved as ONE lattice (the desktop grid's
+ /// primitive), the rails between them the plate's face, and the origin
+ /// axes two grooves engraved down the rail centrelines beside row and
+ /// column 0. Pure shading: no colour is mixed in, so the pane stays the
+ /// same material as every other plate however wide the rails are. (The
+ /// grid was tinted quads until 2026-09-20 — gap-coloured rails at the
+ /// network opacity — which at the designer's rail widths covered 40% of
+ /// the pane in a second colour; no gap colour could match the plate.)
+ ///
+ /// The wall runs from each cell edge outward over half the narrower gap,
+ /// so a rail carries one wall from each side and keeps a flat centre
+ /// (a single carve per cell would stack at the crossings; the lattice
+ /// mitres them). Relief cannot fade, so the network opacity does not
+ /// apply here — it fades wires and text, which are colour.
+ pub fn paint_grid_relief(&self, rect: Rect, pc: &mut PaintCtx) {
+ if self.grid_size_x <= 0.0 || self.grid_size_y <= 0.0 {
+ return;
+ }
+ let step_x = self.grid_size_x + self.skipped_col_w;
+ let step_y = self.grid_size_y + self.skipped_row_h;
+ // Half the narrower rail, the same clamp the desktop grid applies.
+ let run = (self.skipped_col_w.min(self.skipped_row_h) * 0.5).max(1.0);
+
+ if self.show_network_grid && !self.uniform_background && step_x >= 4.0 && step_y >= 4.0 {
+ pc.lattice(
+ rect,
+ (step_x, step_y),
+ (self.grid_origin_x + self.grid_size_x * 0.5, self.grid_origin_y + self.grid_size_y * 0.5),
+ (self.grid_size_x, self.grid_size_y),
+ self.cell_corner_radius(),
+ run,
+ );
+ }
+
+ // Origin axes, down the rail centrelines beside row/column 0 — the
+ // lines the 2px black quads used to draw, now engraved.
+ let axis_w = 2.0;
+ let axis_run = run.min(axis_w);
+ let y_center = self.grid_origin_y + self.grid_size_y + self.skipped_row_h / 2.0;
+ pc.groove((rect.x, y_center), (rect.x + rect.width, y_center), axis_w, axis_run, rect);
+ let x_center = self.grid_origin_x - self.skipped_col_w / 2.0;
+ pc.groove((x_center, rect.y), (x_center, rect.y + rect.height), axis_w, axis_run, rect);
+ }
+
/// The wires / connection preview / grid cells / axes / node bodies / toggles as plain
/// quads — the legacy `extra_quads` body, against `rect` instead of a stored rect —
/// with grid cells tagged by their surviving rounded corners (see [`TaggedQuad`]).
@@ -453,59 +498,9 @@ impl Graph {
}
}
- // Grid lines, on rounded pixel boundaries to prevent seams. The
- // CELLS are not painted: a cell is the pane's own plate showing
- // through, so it wears the plate material exactly — tint, frost,
- // finish — and tracks a retint for free. Only the gaps are drawn,
- // as an overlay in the gap colour at the network opacity. (Until
- // 2026-09-20 every cell was a tinted quad of its own over the plate,
- // which is why the network pane never matched the params pane.)
- if self.show_network_grid && self.grid_size_x > 0.0 && self.grid_size_y > 0.0 && !self.uniform_background {
- let step_y = self.grid_size_y + self.skipped_row_h;
- let step_x = self.grid_size_x + self.skipped_col_w;
-
- if step_y >= 4.0 && step_x >= 4.0 {
- let ry_start = (((rect.y - self.grid_origin_y) / step_y).floor() as i32 - 1).max(-100_000);
- let ry_end = (((rect.y + rect.height - self.grid_origin_y) / step_y).ceil() as i32 + 1).min(100_000);
-
- let cx_start = (((rect.x - self.grid_origin_x) / step_x).floor() as i32 - 1).max(-100_000);
- let cx_end = (((rect.x + rect.width - self.grid_origin_x) / step_x).ceil() as i32 + 1).min(100_000);
-
- let gap_rgba = [self.gap_color[0], self.gap_color[1], self.gap_color[2], self.network_opacity];
- for r in ry_start..=ry_end {
- let y_cell_start = (self.grid_origin_y + (r as f32) * step_y).round();
- let y_cell_end = (self.grid_origin_y + (r as f32) * step_y + self.grid_size_y).round();
- let y2 = (self.grid_origin_y + ((r + 1) as f32) * step_y).round();
-
- let cell_h = y_cell_end - y_cell_start;
- let gap_h = y2 - y_cell_end;
-
- for c in cx_start..=cx_end {
- let x_cell_start = (self.grid_origin_x + (c as f32) * step_x).round();
- let x_cell_end = (self.grid_origin_x + (c as f32) * step_x + self.grid_size_x).round();
- let x2 = (self.grid_origin_x + ((c + 1) as f32) * step_x).round();
-
- let gap_w = x2 - x_cell_end;
-
- // Right gap (shares cell height), then bottom gap (full
- // step width, so it covers the junction), on identical
- // rounded boundaries.
- push_clipped(x_cell_end, y_cell_start, gap_w, cell_h, gap_rgba, &mut quads);
- push_clipped(x_cell_start, y_cell_end, x2 - x_cell_start, gap_h, gap_rgba, &mut quads);
- }
- }
- }
- }
-
- // Origin axes, drawn in the gaps beside row/column 0
- if self.grid_size_x > 0.0 && self.grid_size_y > 0.0 {
- let thickness = 2.0;
- let y_center = self.grid_origin_y + self.grid_size_y + self.skipped_row_h / 2.0;
- push_clipped(rect.x, y_center - thickness / 2.0, rect.width, thickness, [0.0, 0.0, 0.0, 1.0 * self.network_opacity], &mut quads);
-
- let x_center = self.grid_origin_x - self.skipped_col_w / 2.0;
- push_clipped(x_center - thickness / 2.0, rect.y, thickness, rect.height, [0.0, 0.0, 0.0, 1.0 * self.network_opacity], &mut quads);
- }
+ // The grid and the origin axes are RELIEF, not quads — see
+ // `paint_grid_relief`. Hosts that draw these quads themselves call it
+ // at the same point in their own walk.
// Node bodies (culled, not clipped — legacy) + geometry toggles (clipped)
for i in 0..self.nodes.len() {
@@ -713,8 +708,13 @@ impl Paint for Graph {
}
fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
- for (qx, qy, qw, qh, r, c, corners) in self.rounded_geometry(rect) {
+ // The background is the first entry; the grid relief is carved into
+ // it before the wires and nodes go on top.
+ for (i, (qx, qy, qw, qh, r, c, corners)) in self.rounded_geometry(rect).into_iter().enumerate() {
ctx.rounded_rect(Rect { x: qx, y: qy, width: qw, height: qh }, r, corners, c);
+ if i == 0 {
+ self.paint_grid_relief(rect, ctx);
+ }
}
for (cx, cy, r, c) in self.port_circles(rect) {
ctx.circle(cx, cy, r, c);
@@ -1178,6 +1178,9 @@ impl Graph {
}
impl GraphController for Graph {
+ fn paint_grid_relief(&self, rect: Rect, pc: &mut PaintCtx) {
+ Graph::paint_grid_relief(self, rect, pc)
+ }
fn set_nodes(&mut self, nodes: &[GraphNode]) {
// Hover carries a node INDEX, so remap it by id across the rebuild
// instead of clearing — hosts (the designer) re-sync nodes on EVERY
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 88499e2..14687ed 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -646,6 +646,10 @@ pub trait GraphController {
/// rounded corners — for hosts that draw the graph's quads themselves
/// (the designer) and want cells as superellipse tiles.
fn geometry_quads_tagged(&self, rect: crate::scene::layout::Rect) -> Vec<TaggedQuad>;
+ /// The grid and origin axes as relief carved into what is beneath — for
+ /// hosts that draw the graph's quads themselves, called at the point in
+ /// their walk where the grid used to be. Default: no grid.
+ fn paint_grid_relief(&self, _rect: crate::scene::layout::Rect, _pc: &mut crate::scene::paint::PaintCtx) {}
/// The pixel rect of the cell an in-flight node drag will deposit on
/// (`commit_drag`'s resolution), for hosts' drop-target highlight.
/// None outside a node drag.