GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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();