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

commit0e28ae44cc91e330e75b5bbed95ec5cb300b85b8
parent80f2fd053d
authorLucas Galante <[email protected]>
date2026-07-16 20:59
feat: plate children clip at the plate's rounded corners

The graph content and breadcrumb clip to the network plate's rounded
rect, and the params/spreadsheet panes' content (rows, scrollbar,
labels) to their own plates, via the new PaintCtx::clip_rounded — a
plate_rounded_clip helper maps widget slots to their plate rect +
corner radius, applied in both paint_widget and append_frame_text.
Popovers still render unclipped (append_popovers), and the circular
network pane keeps its circle clip. vk-smoke spans gain the new
TextSpan clip_extents field.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/render.rs   | 42 ++++++++++++++++++++++++++++++++++++++++++
 src/vk_smoke.rs |  3 +++
 2 files changed, 45 insertions(+)

diff --git a/src/render.rs b/src/render.rs
index a51c023..99b74af 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -30,6 +30,30 @@ fn merge_bounds(a: Option<[f32; 4]>, b: Option<[f32; 4]>) -> Option<[f32; 4]> {
 }
 
 impl State {
+    /// The rounded-rect clip (plate rect + corner radius) a widget's content must stay
+    /// inside, so children of plates cut off at the plate's rounded corners: the network
+    /// plate for the network pane's parts (graph content, breadcrumb), the pane's own
+    /// plate for the params and spreadsheet panes. `None` with square corners, and for
+    /// the circular network pane (which clips by circle instead).
+    fn plate_rounded_clip(&self, idx: usize) -> Option<(Rect, f32)> {
+        let r = cce_ui::layout::plate_corner_radius();
+        if r <= 0.0 {
+            return None;
+        }
+        let (x, y, w, h) = match idx {
+            CONTENT_IDX | BREADCRUMB_IDX if !self.circular_network_pane => {
+                self.positions[NETWORK_PANEL_IDX]
+            }
+            PARAM_IDX => self.positions[PARAM_IDX],
+            SPREADSHEET_IDX => self.positions[SPREADSHEET_IDX],
+            _ => return None,
+        };
+        if w <= 0.0 || h <= 0.0 {
+            return None;
+        }
+        Some((rect(x, y, w, h), r))
+    }
+
     /// The frame's entire 2D content — geometry AND text — as one display list (the
     /// engine's single paint path; `Application::display_list_text` opts the designer's
     /// text into the engine's shaping/glyph pass, so the app-side FontSystem and buffer
@@ -137,6 +161,13 @@ impl State {
             pc.push_clip_circle(c);
         }
 
+        // Children of plates cut off at the plate's rounded corners (the param pane's
+        // popovers escape this: they render later via `append_popovers`).
+        let rounded_clip = self.plate_rounded_clip(idx);
+        if let Some((rc, rr)) = rounded_clip {
+            pc.push_clip_rounded(rc, rr);
+        }
+
         if idx == NETWORK_PANEL_IDX && self.circular_network_pane {
             let cx = self.circular_network_layout.x;
             let cy = self.circular_network_layout.y;
@@ -258,6 +289,9 @@ impl State {
             }
         }
 
+        if rounded_clip.is_some() {
+            pc.pop_clip_rounded();
+        }
         if active_circle.is_some() {
             pc.pop_clip_circle();
         }
@@ -392,6 +426,11 @@ impl State {
                 None
             };
 
+            // Labels are plate children too — clip them at the plate's rounded corners.
+            let rounded_clip = self.plate_rounded_clip(i);
+            if let Some((rc, rr)) = rounded_clip {
+                pc.push_clip_rounded(rc, rr);
+            }
             let mut scratch = PaintCtx::new();
             append_widget_text(&self.ui_context, w, &mut scratch);
             for item in scratch.finish().items {
@@ -407,6 +446,9 @@ impl State {
                     pc.text_faded(text, x, y, font_size, color, alpha, font, merge_bounds(widget_bounds, label_bounds));
                 }
             }
+            if rounded_clip.is_some() {
+                pc.pop_clip_rounded();
+            }
         }
 
     }
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index 660968e..16aacb8 100644
--- a/src/vk_smoke.rs
+++ b/src/vk_smoke.rs
@@ -413,6 +413,7 @@ fn main() {
                 default_color: [0.92, 0.92, 0.95, 1.0],
                 rotation: None,
                 clip_circle: [0.0; 3],
+                clip_extents: [0.0; 2],
             },
             TextSpan {
                 buffer: &body_buf,
@@ -424,6 +425,7 @@ fn main() {
                 default_color: [pulse, 0.80, 0.55, 1.0],
                 rotation: None,
                 clip_circle: [0.0; 3],
+                clip_extents: [0.0; 2],
             },
             TextSpan {
                 buffer: &clipped_buf,
@@ -439,6 +441,7 @@ fn main() {
                 default_color: [0.70, 0.85, 1.00, 1.0],
                 rotation: None,
                 clip_circle: [0.0; 3],
+                clip_extents: [0.0; 2],
             },
         ];
         // build_scene vertex layout: blur plate is the last 6 verts; the image