git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitfb83c0ceeaa385df27c7d4c949091c0a9aa906d9
parent7e9f753559
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 21:16
feat(network): node names get a compressed floor

With node_compression set, each node name the network draws stands on a
floor of the node bodies' material, as each params row does under
param.backdrop_compression, so a name over a bright model stays readable
when the pane plate is faint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md     | 16 ++++++++++++++++
 src/dialog.rs |  2 +-
 src/render.rs | 43 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 83477c2..aefe7ee 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2712,6 +2712,22 @@ Until then the graph filled itself with `cell_color` (chosen by a
 `gap_color`; all three keys are retired, reported by path at load and not
 read.
 The next section is about dropping the pane plate entirely.
+
+**Floors: a faint plate, compressed content** (since 2026-10-06). The way
+to make the plates recede and keep what is on them readable is to compress
+the scene under the CONTENT rather than under the whole plate:
+`style.surface.param.backdrop_compression` puts a floor of the pane
+material under each params row (cce-ui's `paint_row_floors`), and
+`style.surface.graph.node_compression` sets the node bodies' compression
+and, set at all, puts the same floor behind each node's NAME
+(`State::node_label_floors`, from the graph's own laid-out text, drawn in
+the bodies' run so they share its blur snapshot). Compression pulls the
+backdrop's luminance toward the material's tint RGB and ignores its alpha,
+so a DARK pane colour at a low alpha (`#2020280d`) is a plate that barely
+shows and a dark key under light ink: the mid-grey `#6c6c7b` it replaced
+capped full compression at about 3.3:1 against `#ccccd4`. 0.85 was where
+labels read over a bright model in a shadow session; 0.6 left them grey
+on grey.
 The params widget alone also reads `style.surface.param.backdrop_compression`.
 The rules live in cce-ui's CLAUDE.md ("There is one roll width", "The
 relief is two shapes", "Frost is one block"); this is the designer's view of
diff --git a/src/dialog.rs b/src/dialog.rs
index bd8ee62..7f9fd24 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -902,7 +902,7 @@ impl Layout for Dialog {
 /// through usvg's font database and measured a choice's value some 8 px
 /// wider than it drew, which put the right arrow twice as far from it as
 /// the left; it is the fallback for a font system that shapes nothing.
-fn shaped_width(text: &str, family: &str, font_size: f32) -> f32 {
+pub(crate) fn shaped_width(text: &str, family: &str, font_size: f32) -> f32 {
     cce_ui::geometry_font_system()
         .lock()
         .ok()
diff --git a/src/render.rs b/src/render.rs
index 47a1d1a..84d19c5 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -510,6 +510,13 @@ impl State {
             // at the network opacity, a flat blue-grey wash that predated
             // the plate and made the network pane the one pane with a hue.
 
+            // A node's name hangs off its body onto whatever is behind the
+            // pane, so with the plate faint it stands on the bare scene. With
+            // `node_compression` set it gets a floor of the bodies' material,
+            // as each params row does (`param.backdrop_compression`).
+            let label_floors =
+                if self.node_compression.is_some() { self.node_label_floors(w) } else { Vec::new() };
+
             pc.clip(clip, |pc| {
                 // Node bodies wear the parameter plate's fill exactly — same
                 // tint, opacity, and blur-behind marker (param_plate_fill) —
@@ -616,6 +623,10 @@ impl State {
                 // what still says so while the node is selected and its
                 // roll is the selection's colour.
                 let mut bars: Vec<cce_ui::scene::layout::Rect> = Vec::new();
+                // In the bodies' run, so they share its blur snapshot.
+                for (floor, r) in &label_floors {
+                    pc.fill_material(*floor, (*r, *r, *r, *r), &node_mat);
+                }
                 for (qx, qy, qw, qh, highlighted, off) in bodies {
                     if highlighted {
                         pc.bevel_tinted(rect(qx, qy, qw, qh), radii, &node_mat, node_bevel, hl_tint);
@@ -986,6 +997,38 @@ impl State {
     /// graph's clamped to the network pane (and distance-filtered against the circular
     /// pane), network text fading with `network_opacity`. Popovers follow in
     /// `append_popovers`.
+    /// A floor behind each node name a network editor draws, with its corner
+    /// radius: the labels as the graph lays them out (read back from its
+    /// text, so the left-hand flip is the widget's own), measured as
+    /// shaped, padded a little. A name the circular pane drops in
+    /// `append_frame_text` gets no floor either.
+    fn node_label_floors(&self, w: &dyn WidgetHost) -> Vec<(Rect, f32)> {
+        let (ncx, ncy, ncr) =
+            (self.circular_network_layout.x, self.circular_network_layout.y, self.circular_network_layout.r);
+        let mut scratch = PaintCtx::new();
+        append_widget_text(&self.ui_context, w, &mut scratch);
+        scratch
+            .finish()
+            .items
+            .into_iter()
+            .filter_map(|item| {
+                let Prim::Text { text, x, y, font_size, font, .. } = item.prim else { return None };
+                if self.circular_network_pane && (x - ncx).powi(2) + (y - ncy).powi(2) > ncr * ncr {
+                    return None;
+                }
+                let family = match &font {
+                    Some(f) => cce_ui::layout::split_font_string(f).0.to_string(),
+                    None => cce_ui::layout::graph_node_font_parsed().0,
+                };
+                let tw = crate::dialog::shaped_width(&text, &family, font_size);
+                let (px, py) = (font_size * 0.4, font_size * 0.2);
+                let h = font_size + 2.0 * py;
+                let r = cce_ui::layout::control_corner_radius().min(h * 0.5);
+                Some((rect(x - px, y - py, tw + 2.0 * px, h), r))
+            })
+            .collect()
+    }
+
     fn append_frame_text(&self, pc: &mut PaintCtx) {
         let circular = self.circular_network_pane;
         let ncx = self.circular_network_layout.x;