git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitb34b5c70d0a428ccccbd2e9be73fd4e7d6c0ced3
parent5157537540
authorLucas Galante <[email protected]>
date2026-07-07 20:34
feat(scene): renders_own_subtree escape hatch for legacy subtree painters (Phase 3)

Some widgets (TreeList, and graph-like widgets) draw their own geometry AND recurse into children in a single all_rounded_quads/all_quads override. The paint walk's per-widget paint_self can't reproduce that without double-drawing children. Add Element::renders_own_subtree() (default false): when true, the walk emits the widget's recursive all_rounded_quads/all_quads/arcs/circles directly and does not descend. Flag TreeList (fixes its missing row backgrounds/separators under CCE_PAINT_WALK). Transitional — such widgets can graduate to a non-recursive paint_self later. 126 tests.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

 src/scene/painter.rs             | 19 +++++++++++++++++++
 src/widget/container/treelist.rs |  6 ++++++
 src/widget/mod.rs                |  8 ++++++++
 3 files changed, 33 insertions(+)

diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index df7356a..0e36d1f 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -34,6 +34,25 @@ fn paint_node(ui: &UiContext, ptr: ElemPtr, pc: &mut PaintCtx) {
         if !(*ptr).visible() {
             return;
         }
+
+        // Legacy subtree painters (e.g. TreeList) render their own geometry AND their children via
+        // a recursive all_rounded_quads/all_quads; emit those directly and stop descending.
+        if (*ptr).renders_own_subtree() {
+            for (x, y, w, h, r, c, corners) in (*ptr).all_rounded_quads(ui) {
+                pc.rounded_rect(Rect { x, y, width: w, height: h }, r, corners, c);
+            }
+            for (x, y, w, h, c) in (*ptr).all_quads(ui) {
+                pc.quad(Rect { x, y, width: w, height: h }, c);
+            }
+            for (cx, cy, r, t, s, e, c) in (*ptr).extra_arcs() {
+                pc.arc(cx, cy, r, t, s, e, c);
+            }
+            for (cx, cy, r, c) in (*ptr).extra_circles() {
+                pc.circle(cx, cy, r, c);
+            }
+            return;
+        }
+
         (*ptr).paint_self(ui, pc);
 
         let children = (*ptr).children(ui);
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 215f08d..ad090d4 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -1196,6 +1196,12 @@ impl Element for TreeList {
         list
     }
 
+    // TreeList draws its rows, backgrounds and separators in the recursive all_rounded_quads below,
+    // so the Phase 3 paint walk emits that directly instead of descending (see Element docs).
+    fn renders_own_subtree(&self) -> bool {
+        true
+    }
+
     fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
         let mut quads = Vec::new();
         let (r1, r2, r3, r4) = self.rounded_corners();
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 91c8741..9227b2f 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -528,6 +528,14 @@ pub trait Element {
     /// containers). Default: no clipping.
     fn clips_children(&self) -> bool { false }
 
+    /// Whether this widget paints its ENTIRE subtree itself through its (recursive)
+    /// `all_rounded_quads` / `all_quads` — a legacy "subtree painter" such as `TreeList`, whose
+    /// row backgrounds and separators live in an `all_rounded_quads` override that also recurses
+    /// into its children. When true, the paint walk emits those directly and does NOT recurse
+    /// (the widget already did). Transitional: such widgets will eventually get a proper
+    /// non-recursive `paint_self`. Default: false.
+    fn renders_own_subtree(&self) -> bool { false }
+
     fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
         if !self.visible() {
             return Vec::new();