git.lucas.co / cce-cloud
cloud storage client
git clone https://git.lucas.co/cce-cloud.git

commita32bba2431860505ee3c05a23ba2d19c659e9619
parent530748ce9e
authorLucas Galante <[email protected]>
date2026-08-01 23:33
feat: json popup children render through the paint walk — toolkit bevels flow through

JsonLayoutWidget::paint now runs each active-page child's paint_self and
re-emits its geometry prims (Text skipped — own_labels_with_bounds still
serves every label), replacing the all_rounded_quads + aggregate_quads
flattening that reduced controls to flat fills. Buttons get their beveled
plates, sliders the carved groove and sphere-lit knob, checkboxes their
rims. Page filtering and the panel clip mirror the dissolved
aggregate_quads bounds; the clip rides the walk's scissor batches.

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

 src/json_layout.rs | 109 ++++++++++++++++++++++++++---------------------------
 1 file changed, 53 insertions(+), 56 deletions(-)

diff --git a/src/json_layout.rs b/src/json_layout.rs
index 62adf9a..67443be 100644
--- a/src/json_layout.rs
+++ b/src/json_layout.rs
@@ -342,55 +342,6 @@ impl JsonLayoutWidget {
         (self.base.x, self.base.y, self.base.w, self.base.h)
     }
 
-    /// The page-filtered plain-quad aggregate (the old `WidgetHost::all_quads` override):
-    /// active-page children's backgrounds, decoration quads, and highlight, clipped to
-    /// the content area. External readers reach it through the adapter's reverse bridge
-    /// (`jl.all_quads(ctx)` serves `paint`'s plain prims, which come from here).
-    fn aggregate_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-
-        let active_page = self.active_page;
-        let (bx, by, bw, bh) = self.rect();
-        let pad_x = 16.0;
-        let min_x = bx + pad_x - 4.0;
-        let max_x = bx + bw;
-        let min_y = by;
-        let max_y = by + bh;
-
-        let push_clipped = |qx: f32, qy: f32, qw: f32, qh: f32, qc: [f32; 4], q: &mut Vec<(f32, f32, f32, f32, [f32; 4])>| {
-            let rx1 = qx.max(min_x);
-            let ry1 = qy.max(min_y);
-            let rx2 = (qx + qw).min(max_x);
-            let ry2 = (qy + qh).min(max_y);
-            let rw = rx2 - rx1;
-            let rh = ry2 - ry1;
-            if rw > 0.0 && rh > 0.0 {
-                q.push((rx1, ry1, rw, rh, qc));
-            }
-        };
-
-        for w in &self.widgets {
-            if w.page_idx != active_page {
-                continue;
-            }
-            let (wx, wy, ww, wh) = w.widget.as_dyn().rect();
-            let has_rounded = w.widget.as_dyn().corner_style().1 != (false, false, false, false);
-            if !has_rounded {
-                push_clipped(wx, wy, ww, wh, w.widget.as_dyn().color(), &mut quads);
-            }
-            for q in w.widget.as_dyn().all_quads(ctx) {
-                if has_rounded && (q.0 - wx).abs() < 0.1 && (q.1 - wy).abs() < 0.1 && (q.2 - ww).abs() < 0.1 && (q.3 - wh).abs() < 0.1 {
-                    continue;
-                }
-                push_clipped(q.0, q.1, q.2, q.3, q.4, &mut quads);
-            }
-            if let Some(hq) = w.widget.as_dyn().highlight_quad(ctx) {
-                push_clipped(hq.0, hq.1, hq.2, hq.3, hq.4, &mut quads);
-            }
-        }
-        quads
-    }
-
     /// Per-frame child state (the old `WidgetHost::tick` override): active-page widgets only.
     fn tick_children(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
         let mut changed = false;
@@ -611,19 +562,65 @@ impl cce_ui::widget::Paint for JsonLayoutWidget {
 
     fn paint(&self, _rect: cce_ui::scene::layout::Rect, pc: &mut cce_ui::scene::paint::PaintCtx) {
         use cce_ui::scene::layout::Rect;
+        use cce_ui::scene::paint::{PaintCtx, Prim};
         // The children are Phase 5 Adapted leaves: nothing their all_* getters or the
         // label walk reads comes from the routing context, so a fresh one stands in for
         // the ctx `Paint::paint` does not carry.
         let dummy = UiContext::new();
-        // Rounded: the deleted WidgetHost default's shape — no own background (transparent,
-        // sharp corners), every child unfiltered, in `widgets` order.
+        // Children through the real paint walk, geometry only: bevel/recess/plate prims
+        // survive to the tessellator where the old quad bridges flattened them to fills.
+        // Text prims are skipped — every label, container-owned and child-owned alike,
+        // is served by `own_labels_with_bounds` below, and emitting the children's own
+        // labels here as well would double them. Page filtering and the panel clip
+        // mirror the dissolved `aggregate_quads` bounds.
+        let (bx, by, bw, bh) = self.rect();
+        let pad_x = 16.0;
+        let clip = Rect { x: bx + pad_x - 4.0, y: by, width: bw - (pad_x - 4.0), height: bh };
         for w in &self.widgets {
-            for (x, y, qw, qh, r, c, corners) in w.widget.as_dyn().all_rounded_quads(&dummy) {
-                pc.rounded_rect(Rect { x, y, width: qw, height: qh }, r, corners, c);
+            if w.page_idx != self.active_page {
+                continue;
             }
-        }
-        for (x, y, w, h, c) in self.aggregate_quads(&dummy) {
-            pc.quad(Rect { x, y, width: w, height: h }, c);
+            let mut tmp = PaintCtx::new();
+            w.widget.as_dyn().paint_self(&dummy, &mut tmp);
+            pc.clip(clip, |pc| {
+                for item in tmp.finish().items {
+                    let clip_circle = item.clip_circle;
+                    if let Some(c) = clip_circle {
+                        pc.push_clip_circle(c);
+                    }
+                    match item.prim {
+                        Prim::Text { .. } => {}
+                        Prim::Quad { rect, color } => pc.quad(rect, color),
+                        Prim::RoundedRect { rect, radius, corners, color } => pc.rounded_rect(rect, radius, corners, color),
+                        Prim::Border { rect, radii, fill, border, thickness } => pc.border(rect, radii, fill, border, thickness),
+                        Prim::Bevel { rect, radii, color, depth, tint } => pc.bevel_tinted(rect, radii, color, depth, tint),
+                        Prim::Recess { rect, radii, depth, edges, tint } => match tint {
+                            Some(t) => pc.recess_tinted(rect, radii, depth, t),
+                            None => pc.recess_edges(rect, radii, depth, edges),
+                        },
+                        Prim::Boss { rect, radii, depth, edges, tint } => match tint {
+                            Some(t) => pc.boss_edges_tinted(rect, radii, depth, edges, t),
+                            None => pc.boss_edges(rect, radii, depth, edges),
+                        },
+                        Prim::Ridge { rect, radii, depth, edges } => pc.ridge_edges(rect, radii, depth, edges),
+                        Prim::Plate { rect, radii, color, depth } => pc.plate(rect, radii, color, depth),
+                        Prim::Arc { cx, cy, radius, thickness, start, end, color } => pc.arc(cx, cy, radius, thickness, start, end, color),
+                        Prim::ArcShaded { cx, cy, radius, thickness, start, end, inner, crest, outer } => {
+                            pc.arc_shaded(cx, cy, radius, thickness, start, end, inner, crest, outer)
+                        }
+                        Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => pc.vector(x1, y1, x2, y2, thickness, color, cap),
+                        Prim::Circle { cx, cy, radius, color } => pc.circle(cx, cy, radius, color),
+                        Prim::Sphere { cx, cy, radius, color } => pc.sphere(cx, cy, radius, color),
+                        Prim::ConcaveFillet { cx, cy, radius, depth, start, raised } => {
+                            pc.concave_fillet(cx, cy, radius, depth, start, raised)
+                        }
+                        Prim::Image { image, rect, alpha } => pc.image(image, rect, alpha),
+                    }
+                    if clip_circle.is_some() {
+                        pc.pop_clip_circle();
+                    }
+                }
+            });
         }
         for (tl, bounds) in self.own_labels_with_bounds(&dummy) {
             pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, None, bounds);