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

commit4e6970748f8c542d5ad2d3a6dbccfb39b64f5818
parent950891ff71
authorLucas Galante <[email protected]>
date2026-07-11 16:46
refactor(widget)!: DELETE the per-widget text getters from Element (Phase 6ar)

text_labels / text_labels_with_bounds / text_labels_with_font_and_bounds /
get_text_items are gone from the trait, along with Adapted's impls, the
Paint::text_items hook, and every container aggregate (Backplate, Layer,
Page, Plate, SectionContainer, ColorRamp, ControlPanel, JsonLayout,
ButtonStrip). Every widget now reaches the frame through paint_self:

- New painter helpers keep the deleted defaults' semantics without the
  trait: paint_legacy_leaf (leaf geometry + passed-in labels),
  fonted_leaf_labels (widget_font + scroll-ancestor clamp),
  scroll_ancestor_text_bounds, base_control_label.
- Legacy leaves (Trackpad, KeybindRecorder, FontSelector, ColorSelector,
  SectionHeader, ButtonStrip, List) keep their label logic as inherent
  own_labels()/own_fonted_labels() feeding paint_self overrides.
- Composites: TreeList's recursive labels are inherent
  subtree_fonted_labels (concrete Adapted children read via the now
  pub(crate) own_labels_with_font_and_bounds); ControlPanel and
  ParametersBg source dyn-children labels off the paint walk (ControlPanel
  re-applies its scroll shift + viewport clamp); JsonLayout reads child
  text off the walk inside its page-filtered aggregate; List/ControlPanel
  are renders_own_subtree (scroll frames — descent would desync at
  scroll ≠ 0).
- Tests moved to the inherent/walk equivalents; StatusBar's manual-host
  buffer test now asserts the buffer lifecycle directly.

Verified live: settings audio, email, test-interface gallery, dm greeter,
designer default+circular, cloud fuzzel+json, colors, fonts, data-editor
empty+loaded — AE=0 or cursor/translucency/status-race noise only; 176
tests; full workspace builds.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 src/scene/painter.rs                      |  88 +++++-
 src/widget/container/backplate.rs         |  82 +-----
 src/widget/container/control_panel.rs     |  64 +++--
 src/widget/container/layer.rs             |  50 +---
 src/widget/container/menu.rs              |   2 +-
 src/widget/container/page.rs              |  83 +-----
 src/widget/container/parameters_bg.rs     |  43 ++-
 src/widget/container/plate.rs             |  87 +-----
 src/widget/container/scrolling_list.rs    | 244 ++++++++--------
 src/widget/container/section_container.rs |  38 +--
 src/widget/container/treelist.rs          | 456 ++++++++++++++----------------
 src/widget/display/label.rs               |  35 ++-
 src/widget/display/status_bar.rs          |  32 +--
 src/widget/input/button.rs                |   2 +-
 src/widget/input/button_strip.rs          |  93 +++---
 src/widget/input/checkbox.rs              |   4 +-
 src/widget/input/color_selector.rs        |  46 +--
 src/widget/input/dropdown.rs              |   2 +-
 src/widget/input/font_selector.rs         |  62 ++--
 src/widget/input/keybind_recorder.rs      |  26 +-
 src/widget/input/ramp.rs                  |  47 ---
 src/widget/input/slider.rs                |   2 +-
 src/widget/input/trackpad.rs              | 112 ++++----
 src/widget/json_layout.rs                 |  94 +++---
 src/widget/mod.rs                         | 100 +------
 src/widget/model.rs                       |  85 +-----
 26 files changed, 807 insertions(+), 1172 deletions(-)

diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index 75214cc..743a5ba 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -14,7 +14,7 @@
 
 use crate::scene::layout::Rect;
 use crate::scene::paint::{DisplayList, PaintCtx, Prim};
-use crate::widget::{Element, UiContext};
+use crate::widget::{Element, TextLabel, UiContext};
 
 type ElemPtr = *mut (dyn Element + 'static);
 
@@ -65,6 +65,92 @@ pub fn append_widget_text(ui: &UiContext, root: &dyn Element, pc: &mut PaintCtx)
     }
 }
 
+/// The `Element` default `paint_self`'s LEAF branch as a reusable body: leaf geometry
+/// (rounded quads, plain quads, arcs, circles) followed by the widget's fonted labels.
+/// Legacy leaf widgets' `paint_self` overrides call this with their own labels — the
+/// labels are PASSED IN rather than fetched through the per-widget text getters, so this
+/// helper (and every override built on it) survives the getters' deletion. `pub` so
+/// app-local legacy widgets (display-manager's status/session widgets, cloud's fuzzel)
+/// can use it too.
+pub fn paint_legacy_leaf(
+    w: &dyn Element,
+    ui: &UiContext,
+    pc: &mut PaintCtx,
+    labels: Vec<(TextLabel, Option<String>, Option<[f32; 4]>)>,
+) {
+    for (x, y, qw, qh, r, c, corners) in w.all_rounded_quads(ui) {
+        pc.rounded_rect(Rect { x, y, width: qw, height: qh }, r, corners, c);
+    }
+    for (x, y, qw, qh, c) in w.all_quads(ui) {
+        pc.quad(Rect { x, y, width: qw, height: qh }, c);
+    }
+    for (cx, cy, r, t, s, e, c) in w.extra_arcs() {
+        pc.arc(cx, cy, r, t, s, e, c);
+    }
+    for (cx, cy, r, c) in w.extra_circles() {
+        pc.circle(cx, cy, r, c);
+    }
+    for (tl, font, bounds) in labels {
+        pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, bounds);
+    }
+}
+
+/// The scroll-ancestor text clamp the deleted default fonted getter applied: the nearest
+/// ScrollBox/List ancestor's viewport, if any.
+pub fn scroll_ancestor_text_bounds(w: &dyn Element, ui: &UiContext) -> Option<[f32; 4]> {
+    let mut curr = w.parent(ui);
+    while let Some(parent_ptr) = curr {
+        let parent = unsafe { &*parent_ptr };
+        if let Some(scroll_box) = parent.as_any().downcast_ref::<crate::widget::ScrollBox>() {
+            let (sb_x, _, sb_w, _) = scroll_box.rect();
+            let view_min = scroll_box.viewport_y + 4.0;
+            let view_max = scroll_box.viewport_y + scroll_box.viewport_h - 4.0;
+            return Some([sb_x, view_min, sb_x + sb_w, view_max]);
+        } else if let Some(list) = parent.as_any().downcast_ref::<crate::widget::List>() {
+            let (sb_x, _, sb_w, _) = list.rect();
+            let view_min = list.scroll_box.viewport_y + 4.0;
+            let view_max = list.scroll_box.viewport_y + list.scroll_box.viewport_h - 4.0;
+            return Some([sb_x, view_min, sb_x + sb_w, view_max]);
+        }
+        curr = parent.parent(ui);
+    }
+    None
+}
+
+/// The deleted `Element::text_labels` default's base-label synthesis: the control label
+/// stored on the widget base, positioned by the configured control-label layout. For
+/// legacy widgets whose only text was that label (List's columns=None frame).
+pub fn base_control_label(w: &dyn Element) -> Vec<TextLabel> {
+    if let Some(b) = w.base() {
+        if let Some(ref label) = b.label {
+            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+            let color = crate::colors::control_label_color_detached_for_state(b.hovered, b.focused);
+            if crate::layout::control_label_layout() == "side" {
+                let label_x = w.label_x_offset();
+                if label_x > 0.0 {
+                    let y_pos = crate::layout::align_text_y(b.y, b.h, font_size, 0.0);
+                    return vec![TextLabel { text: label.clone(), x: b.x + 4.0, y: y_pos, font_size, color }];
+                }
+            }
+            return vec![TextLabel { text: label.clone(), x: b.x, y: b.y, font_size, color }];
+        }
+    }
+    Vec::new()
+}
+
+/// Map a legacy leaf's own plain labels to the (label, font, bounds) triples the deleted
+/// default fonted getter produced: the widget's control font on every label plus the
+/// scroll-ancestor clamp.
+pub fn fonted_leaf_labels(
+    w: &dyn Element,
+    ui: &UiContext,
+    labels: Vec<TextLabel>,
+) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+    let font = w.widget_font();
+    let bounds = scroll_ancestor_text_bounds(w, ui);
+    labels.into_iter().map(|l| (l, font.clone(), bounds)).collect()
+}
+
 fn paint_node(ui: &UiContext, ptr: ElemPtr, pc: &mut PaintCtx) {
     unsafe {
         if !(*ptr).visible() {
diff --git a/src/widget/container/backplate.rs b/src/widget/container/backplate.rs
index e844a99..8eb1a16 100644
--- a/src/widget/container/backplate.rs
+++ b/src/widget/container/backplate.rs
@@ -1,6 +1,6 @@
 use crate::widget::*;
 use crate::context::UiContext;
-use crate::widget::display::TextLabel;
+
 
 #[derive(Debug, Clone)]
 pub struct Backplate {
@@ -251,86 +251,6 @@ impl Element for Backplate {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            labels.extend(widget.text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            for (label, bounds) in widget.text_labels_with_bounds(ctx) {
-                let cb = if let Some(b) = bounds {
-                    let cx0 = b[0].max(wx);
-                    let cy0 = b[1].max(wy);
-                    let cx1 = b[2].min(wx + ww);
-                    let cy1 = b[3].min(wy + wh);
-                    if cx1 > cx0 && cy1 > cy0 {
-                        Some([cx0, cy0, cx1, cy1])
-                    } else {
-                        continue;
-                    }
-                } else {
-                    Some([wx, wy, wx + ww, wy + wh])
-                };
-                result.push((label, cb));
-            }
-        }
-        result
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            for (label, font, bounds) in widget.text_labels_with_font_and_bounds(ctx) {
-                let cb = if let Some(b) = bounds {
-                    let cx0 = b[0].max(wx);
-                    let cy0 = b[1].max(wy);
-                    let cx1 = b[2].min(wx + ww);
-                    let cy1 = b[3].min(wy + wh);
-                    if cx1 > cx0 && cy1 > cy0 {
-                        Some([cx0, cy0, cx1, cy1])
-                    } else {
-                        continue;
-                    }
-                } else {
-                    Some([wx, wy, wx + ww, wy + wh])
-                };
-                result.push((label, font, cb));
-            }
-        }
-        result
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.get_text_items());
-        }
-        result
-    }
-
     fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
         if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
             return false;
diff --git a/src/widget/container/control_panel.rs b/src/widget/container/control_panel.rs
index 363cef5..004877d 100644
--- a/src/widget/container/control_panel.rs
+++ b/src/widget/container/control_panel.rs
@@ -79,6 +79,18 @@ impl Element for ControlPanel {
         self.children.clone()
     }
 
+    // ControlPanel is a legacy scroll frame: children are laid out UNSCROLLED and the
+    // scroll offset is applied at aggregate time (all_* / the fonted getter). The walk
+    // must emit those aggregates and not descend — descending would paint the children
+    // unshifted, desyncing text from geometry as soon as the panel scrolls.
+    fn renders_own_subtree(&self) -> bool {
+        true
+    }
+
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(self, ui, ctx, self.scrolled_child_labels(ui));
+    }
+
     fn color(&self) -> [f32; 4] {
         colors::control_panel_color()
     }
@@ -537,17 +549,28 @@ impl Element for ControlPanel {
         false
     }
 
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
+}
+
+impl Drop for ControlPanel {
+    fn drop(&mut self) {
+        clear_widget_references(self);
+    }
+}
+
+impl ControlPanel {
+    /// Children's walk text with the panel's scroll shift and viewport clamp — what the
+    /// deleted fonted getter served: children are laid out UNSCROLLED and the offset is
+    /// an aggregate-time transform.
+    pub(crate) fn scrolled_child_labels(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
         let scroll_y = self.scroll_box.scroll_y;
         let (x, y, w, h) = self.rect();
-        unsafe {
-            for child_ptr in &self.children {
-                let mut child_labels = (**child_ptr).text_labels_with_font_and_bounds(ctx);
-                for (label, _font, bounds) in &mut child_labels {
-                    label.y -= scroll_y;
-                    
-                    let new_bounds = if let Some([l, t, r, b]) = *bounds {
+        let mut labels = Vec::new();
+        for &child_ptr in &self.children {
+            let mut scratch = crate::scene::paint::PaintCtx::new();
+            crate::scene::painter::append_widget_text(ctx, unsafe { &*child_ptr }, &mut scratch);
+            for item in scratch.finish().items {
+                if let crate::scene::paint::Prim::Text { text, x: lx, y: ly, font_size, color, font, bounds, .. } = item.prim {
+                    let new_bounds = if let Some([l, t, r, b]) = bounds {
                         let nl = l.max(x);
                         let nt = (t - scroll_y).max(y);
                         let nr = r.min(x + w);
@@ -556,27 +579,14 @@ impl Element for ControlPanel {
                     } else {
                         Some([x, y, x + w, y + h])
                     };
-                    *bounds = new_bounds;
+                    labels.push((
+                        TextLabel { text, x: lx, y: ly - scroll_y, font_size, color },
+                        font,
+                        new_bounds,
+                    ));
                 }
-                labels.extend(child_labels);
             }
         }
         labels
     }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut items = Vec::new();
-        unsafe {
-            for child_ptr in &self.children {
-                items.extend((**child_ptr).get_text_items());
-            }
-        }
-        items
-    }
-}
-
-impl Drop for ControlPanel {
-    fn drop(&mut self) {
-        clear_widget_references(self);
-    }
 }
diff --git a/src/widget/container/layer.rs b/src/widget/container/layer.rs
index ee39325..1205245 100644
--- a/src/widget/container/layer.rs
+++ b/src/widget/container/layer.rs
@@ -1,6 +1,6 @@
 use crate::widget::*;
 use crate::context::UiContext;
-use crate::widget::display::TextLabel;
+
 
 #[derive(Debug, Clone)]
 pub struct Layer {
@@ -133,54 +133,6 @@ impl Element for Layer {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            labels.extend(widget.text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_bounds(ctx));
-        }
-        result
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_font_and_bounds(ctx));
-        }
-        result
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.get_text_items());
-        }
-        result
-    }
-
     fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
         if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
             return false;
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 054002e..5ca5cae 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -660,7 +660,7 @@ impl Paint for MenuBar {
             ctx.text(display_title, x_pos, text_y, font_size, text_color);
         }
 
-        for l in self.menus.text_labels() {
+        for l in self.menus.own_labels() {
             ctx.text(l.text, l.x, l.y, l.font_size, l.color);
         }
     }
diff --git a/src/widget/container/page.rs b/src/widget/container/page.rs
index 650f608..02e01c5 100644
--- a/src/widget/container/page.rs
+++ b/src/widget/container/page.rs
@@ -1,6 +1,6 @@
 use crate::widget::*;
 use crate::context::UiContext;
-use crate::widget::display::TextLabel;
+
 use super::layer::Layer;
 
 use super::container_layout::ContainerLayout;
@@ -230,87 +230,6 @@ impl Element for Page {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let (_, py, _, ph) = self.rect();
-        let mut result = Vec::new();
-        for label in self.base.text_labels() {
-            if label.y >= py && label.y + label.font_size <= py + ph {
-                result.push(label);
-            }
-        }
-        result
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let (px, py, pw, ph) = self.rect();
-        let page_bounds = [px, py, px + pw, py + ph];
-
-        let mut result = Vec::new();
-        for (label, bounds) in self.base.text_labels_with_bounds(ctx) {
-            let intersected_bounds = if let Some([l, t, r, b]) = bounds {
-                let il = l.max(page_bounds[0]);
-                let it = t.max(page_bounds[1]);
-                let ir = r.min(page_bounds[2]);
-                let ib = b.min(page_bounds[3]);
-                if il < ir && it < ib {
-                    Some([il, it, ir, ib])
-                } else {
-                    continue;
-                }
-            } else {
-                if label.y + label.font_size < py || label.y > py + ph {
-                    continue;
-                }
-                Some(page_bounds)
-            };
-            result.push((label, intersected_bounds));
-        }
-        result
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let (px, py, pw, ph) = self.rect();
-        let page_bounds = [px, py, px + pw, py + ph];
-
-        let mut result = Vec::new();
-        for (label, font, bounds) in self.base.text_labels_with_font_and_bounds(ctx) {
-            let intersected_bounds = if let Some([l, t, r, b]) = bounds {
-                let il = l.max(page_bounds[0]);
-                let it = t.max(page_bounds[1]);
-                let ir = r.min(page_bounds[2]);
-                let ib = b.min(page_bounds[3]);
-                if il < ir && it < ib {
-                    Some([il, it, ir, ib])
-                } else {
-                    continue;
-                }
-            } else {
-                if label.y + label.font_size < py || label.y > py + ph {
-                    continue;
-                }
-                Some(page_bounds)
-            };
-            result.push((label, font, intersected_bounds));
-        }
-        result
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        self.base.get_text_items()
-    }
-
     fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
         if !self.visible {
             return false;
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index b76576b..e6af533 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -237,11 +237,11 @@ impl ParametersBg {
             let r = rects[i];
             if ptype.starts_with("slider") {
                 if let Some(s) = &self.sliders[i] {
-                    labels.extend(s.text_labels());
+                    labels.extend(s.own_text_labels());
                 }
             } else if ptype.starts_with("float3") {
                 if let Some(f) = &self.float3s[i] {
-                    labels.extend(f.text_labels());
+                    labels.extend(f.own_text_labels());
                 }
             } else if ptype == "section" {
                 labels.push(TextLabel {
@@ -277,27 +277,27 @@ impl ParametersBg {
                 });
             } else if ptype.starts_with("spinbox") {
                 if let Some(sb) = &self.spinboxes[i] {
-                    labels.extend(sb.text_labels());
+                    labels.extend(sb.own_text_labels());
                 }
             } else if ptype == "text" {
                 if let Some(tb) = &self.texts[i] {
-                    labels.extend(tb.text_labels());
+                    labels.extend(tb.own_text_labels());
                 }
             } else if ptype.starts_with("choice") {
                 if let Some(d) = &self.choices[i] {
-                    labels.extend(d.text_labels());
+                    labels.extend(d.own_text_labels());
                 }
             } else if ptype == "button" {
                 if let Some(b) = &self.buttons[i] {
-                    labels.extend(b.text_labels());
+                    labels.extend(b.own_text_labels());
                 }
             } else if ptype == "toggle" || ptype == "checkbox" {
                 if let Some(cb) = &self.checkboxes[i] {
-                    labels.extend(cb.text_labels());
+                    labels.extend(cb.own_text_labels());
                 }
             } else if ptype.starts_with("color") || ptype == "rgb" || ptype == "rgba" {
                 if let Some(c) = &self.colors[i] {
-                    labels.extend(c.text_labels());
+                    labels.extend(c.own_labels());
                 }
             } else {
                 labels.push(TextLabel {
@@ -715,8 +715,15 @@ impl Paint for ParametersBg {
             result.push((l, label_font, bounds));
         }
         for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_font_and_bounds(ctx));
+            // The trait text getters are gone: the raw children's labels come off the
+            // paint walk (fonts + clip bounds composed into the prims).
+            let mut scratch = crate::scene::paint::PaintCtx::new();
+            crate::scene::painter::append_widget_text(ctx, unsafe { &*child_ptr }, &mut scratch);
+            for item in scratch.finish().items {
+                if let crate::scene::paint::Prim::Text { text, x, y, font_size, color, font, bounds, .. } = item.prim {
+                    result.push((TextLabel { text, x, y, font_size, color }, font, bounds));
+                }
+            }
         }
         result
     }
@@ -1953,10 +1960,20 @@ mod tests {
         );
         // The no-double-draw contract of the plain-quad hatch.
         assert!(Element::all_quads(&p, &ctx).is_empty());
-        // Per-label hatch: labels carry the widget font and viewport bounds.
-        let labels = Element::text_labels_with_font_and_bounds(&p, &ctx);
+        // Per-label hatch: the walk's text prims carry the widget font and viewport bounds.
+        let mut scratch = crate::scene::paint::PaintCtx::new();
+        crate::scene::painter::append_widget_text(&ctx, &p, &mut scratch);
+        let labels: Vec<_> = scratch
+            .finish()
+            .items
+            .into_iter()
+            .filter_map(|item| match item.prim {
+                crate::scene::paint::Prim::Text { font, bounds, .. } => Some((font, bounds)),
+                _ => None,
+            })
+            .collect();
         assert!(!labels.is_empty());
-        assert!(labels.iter().all(|(_, font, bounds)| font.is_some() && bounds.is_some()));
+        assert!(labels.iter().all(|(font, bounds)| font.is_some() && bounds.is_some()));
     }
 
     #[test]
diff --git a/src/widget/container/plate.rs b/src/widget/container/plate.rs
index 16c6cc1..30a86ef 100644
--- a/src/widget/container/plate.rs
+++ b/src/widget/container/plate.rs
@@ -1,6 +1,6 @@
 use crate::colors;
 use crate::widget::*;
-use crate::widget::display::TextLabel;
+
 
 #[derive(Debug, Clone)]
 pub struct Plate {
@@ -475,91 +475,6 @@ impl Element for Plate {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.base.label {
-            let (_, font_size) = crate::layout::control_label_font_parsed();
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.base.x,
-                y: self.base.base.y,
-                font_size,
-                color: colors::control_label_color_u8(),
-            });
-        }
-        for &child_ptr in &self.base.children {
-            let widget = unsafe { &*child_ptr };
-            labels.extend(widget.text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        if let Some(ref label) = self.base.base.label {
-            let (_, font_size) = crate::layout::control_label_font_parsed();
-            result.push((
-                TextLabel {
-                    text: label.clone(),
-                    x: self.base.base.x,
-                    y: self.base.base.y,
-                    font_size,
-                    color: colors::control_label_color_u8(),
-                },
-                None,
-            ));
-        }
-        for &child_ptr in &self.base.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_bounds(ctx));
-        }
-        result
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        if let Some(ref label) = self.base.base.label {
-            let (_, font_size) = crate::layout::control_label_font_parsed();
-            result.push((
-                TextLabel {
-                    text: label.clone(),
-                    x: self.base.base.x,
-                    y: self.base.base.y,
-                    font_size,
-                    color: colors::control_label_color_u8(),
-                },
-                None,
-                None,
-            ));
-        }
-        for &child_ptr in &self.base.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_font_and_bounds(ctx));
-        }
-        result
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut items = Vec::new();
-        for &child_ptr in &self.base.children {
-            let widget = unsafe { &*child_ptr };
-            items.extend(widget.get_text_items());
-        }
-        items
-    }
-
     fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
         if !self.visible {
             return;
diff --git a/src/widget/container/scrolling_list.rs b/src/widget/container/scrolling_list.rs
index d5e0a6c..6c45c66 100644
--- a/src/widget/container/scrolling_list.rs
+++ b/src/widget/container/scrolling_list.rs
@@ -490,13 +490,138 @@ impl Element for List {
         quads
     }
 
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
+        if self.search_enabled {
+            let open_key = crate::color::list_open_search_key();
+            let close_key = crate::color::list_close_search_key();
+            
+            if event.state == ElementState::Pressed {
+                if self.search_visible && match_key_shortcut(event, &close_key) {
+                    self.search_visible = false;
+                    self.search_box.text.clear();
+                    self.search_box.edit_buffer.clear();
+                    self.search_box.just_changed = true;
+                    self.search_box.unfocus();
+                    ctx.clear_focus();
+                    
+                    let (lx, ly, lw, lh) = (self.base.x, self.base.y, self.base.w, self.base.h);
+                    self.set_rect(lx, ly, lw, lh);
+                    self.mark_dirty(ctx);
+                    return true;
+                }
+                
+                if !self.search_visible && match_key_shortcut(event, &open_key) {
+                    self.search_visible = true;
+                    let (lx, ly, lw, lh) = (self.base.x, self.base.y, self.base.w, self.base.h);
+                    self.set_rect(lx, ly, lw, lh);
+                    ctx.set_focused(&mut self.search_box);
+                    self.search_box.focus();
+                    self.mark_dirty(ctx);
+                    return true;
+                }
+            }
+            
+            let is_arrow = event.logical_key == Key::Named(NamedKey::ArrowDown) || event.logical_key == Key::Named(NamedKey::ArrowUp);
+            if is_arrow {
+                if self.handle_list_navigation(event, ctx) {
+                    return true;
+                }
+            }
+            
+            if self.search_visible {
+                if self.search_box.keyboard_input(event, ctx) {
+                    return true;
+                }
+            }
+        }
+        
+        if self.scroll_box.keyboard_input(event, ctx) {
+            return true;
+        }
+        if self.handle_list_navigation(event, ctx) {
+            return true;
+        }
+        false
+    }
+
+    fn widget_font(&self) -> Option<String> {
+        let f = crate::layout::list_font();
+        if f.is_empty() {
+            None
+        } else {
+            Some(f)
+        }
+    }
+
+    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> { self.scroll_box.parent(_ctx) }
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
+        self.scroll_box.set_parent(parent, ctx);
+        if parent.is_some() {
+            let self_ptr = self as *mut Self;
+            let self_id = self.base.id();
+            unsafe {
+                let sb_ptr = (*self_ptr).search_box.as_ptr_mut();
+                let sb_id = (*self_ptr).search_box.base().unwrap().id();
+                ctx.register_widget(sb_id, sb_ptr);
+                ctx.link_ids(self_id, sb_id);
+                (*sb_ptr).set_parent(Some(self_ptr), ctx);
+            }
+        }
+    }
+    fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
+        let mut list = self.scroll_box.children(ctx);
+        if self.search_enabled && self.search_visible {
+            let self_ptr = self as *const Self as *mut Self;
+            unsafe {
+                list.push((*self_ptr).search_box.as_ptr_mut());
+            }
+        }
+        list
+    }
+
+    // List renders its own subtree: column-mode rows are List-drawn cells (no child
+    // widgets), and columns=None rows live behind the internal ScrollBox with the
+    // viewport offset/clip applied by the aggregates — the walk emits those and must
+    // not also descend.
+    fn renders_own_subtree(&self) -> bool {
+        true
+    }
+
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(self, ui, ctx, self.own_fonted_labels(ui));
+    }
+    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) { self.scroll_box.add_child(child, ctx); }
+    fn clear_children(&mut self, ctx: &mut UiContext) { self.scroll_box.clear_children(ctx); }
+    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+        if self.search_enabled && self.search_visible {
+            self.search_box.prepare_text(fs);
+        }
+    }
+    fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
+        let mut changed = false;
+        if self.scroll_box.tick(dt, ctx) {
+            changed = true;
+        }
+        if self.search_enabled && self.search_visible {
+            if self.search_box.tick(dt, ctx) {
+                changed = true;
+            }
+        }
+        changed
+    }
+}
+
+unsafe impl Send for List {}
+unsafe impl Sync for List {}
+
+impl List {
+    pub(crate) fn own_fonted_labels(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
         if !self.visible() {
             return Vec::new();
         }
         if self.columns.is_none() {
             let font = self.widget_font();
-            let mut labels = self.text_labels().into_iter().map(|l| (l, font.clone(), None::<[f32; 4]>)).collect::<Vec<_>>();
+            let mut labels = crate::scene::painter::base_control_label(self).into_iter().map(|l| (l, font.clone(), None::<[f32; 4]>)).collect::<Vec<_>>();
             let mut curr = self.parent(ctx);
             let mut scroll_box_bounds = None;
             while let Some(parent_ptr) = curr {
@@ -516,7 +641,7 @@ impl Element for List {
                 }
             }
             if self.search_enabled && self.search_visible {
-                labels.extend(self.search_box.text_labels_with_font_and_bounds(ctx));
+                labels.extend(self.search_box.own_labels_with_font_and_bounds(ctx));
             }
             return labels;
         }
@@ -615,119 +740,8 @@ impl Element for List {
             }
         }
         if self.search_enabled && self.search_visible {
-            result.extend(self.search_box.text_labels_with_font_and_bounds(ctx));
+            result.extend(self.search_box.own_labels_with_font_and_bounds(ctx));
         }
         result
     }
-
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        if self.search_enabled {
-            let open_key = crate::color::list_open_search_key();
-            let close_key = crate::color::list_close_search_key();
-            
-            if event.state == ElementState::Pressed {
-                if self.search_visible && match_key_shortcut(event, &close_key) {
-                    self.search_visible = false;
-                    self.search_box.text.clear();
-                    self.search_box.edit_buffer.clear();
-                    self.search_box.just_changed = true;
-                    self.search_box.unfocus();
-                    ctx.clear_focus();
-                    
-                    let (lx, ly, lw, lh) = (self.base.x, self.base.y, self.base.w, self.base.h);
-                    self.set_rect(lx, ly, lw, lh);
-                    self.mark_dirty(ctx);
-                    return true;
-                }
-                
-                if !self.search_visible && match_key_shortcut(event, &open_key) {
-                    self.search_visible = true;
-                    let (lx, ly, lw, lh) = (self.base.x, self.base.y, self.base.w, self.base.h);
-                    self.set_rect(lx, ly, lw, lh);
-                    ctx.set_focused(&mut self.search_box);
-                    self.search_box.focus();
-                    self.mark_dirty(ctx);
-                    return true;
-                }
-            }
-            
-            let is_arrow = event.logical_key == Key::Named(NamedKey::ArrowDown) || event.logical_key == Key::Named(NamedKey::ArrowUp);
-            if is_arrow {
-                if self.handle_list_navigation(event, ctx) {
-                    return true;
-                }
-            }
-            
-            if self.search_visible {
-                if self.search_box.keyboard_input(event, ctx) {
-                    return true;
-                }
-            }
-        }
-        
-        if self.scroll_box.keyboard_input(event, ctx) {
-            return true;
-        }
-        if self.handle_list_navigation(event, ctx) {
-            return true;
-        }
-        false
-    }
-
-    fn widget_font(&self) -> Option<String> {
-        let f = crate::layout::list_font();
-        if f.is_empty() {
-            None
-        } else {
-            Some(f)
-        }
-    }
-
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> { self.scroll_box.parent(_ctx) }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
-        self.scroll_box.set_parent(parent, ctx);
-        if parent.is_some() {
-            let self_ptr = self as *mut Self;
-            let self_id = self.base.id();
-            unsafe {
-                let sb_ptr = (*self_ptr).search_box.as_ptr_mut();
-                let sb_id = (*self_ptr).search_box.base().unwrap().id();
-                ctx.register_widget(sb_id, sb_ptr);
-                ctx.link_ids(self_id, sb_id);
-                (*sb_ptr).set_parent(Some(self_ptr), ctx);
-            }
-        }
-    }
-    fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        let mut list = self.scroll_box.children(ctx);
-        if self.search_enabled && self.search_visible {
-            let self_ptr = self as *const Self as *mut Self;
-            unsafe {
-                list.push((*self_ptr).search_box.as_ptr_mut());
-            }
-        }
-        list
-    }
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) { self.scroll_box.add_child(child, ctx); }
-    fn clear_children(&mut self, ctx: &mut UiContext) { self.scroll_box.clear_children(ctx); }
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        if self.search_enabled && self.search_visible {
-            self.search_box.prepare_text(fs);
-        }
-    }
-    fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
-        let mut changed = false;
-        if self.scroll_box.tick(dt, ctx) {
-            changed = true;
-        }
-        if self.search_enabled && self.search_visible {
-            if self.search_box.tick(dt, ctx) {
-                changed = true;
-            }
-        }
-        changed
-    }
 }
-
-unsafe impl Send for List {}
-unsafe impl Sync for List {}
diff --git a/src/widget/container/section_container.rs b/src/widget/container/section_container.rs
index f24abc5..a8fd19e 100644
--- a/src/widget/container/section_container.rs
+++ b/src/widget/container/section_container.rs
@@ -1,6 +1,6 @@
 use crate::widget::*;
 use crate::context::UiContext;
-use crate::widget::display::TextLabel;
+
 use super::container::Container;
 use super::container_layout::ContainerLayout;
 
@@ -133,42 +133,6 @@ impl Element for SectionContainer {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.draw_children {
-            return Vec::new();
-        }
-        let mut labels = self.header.text_labels();
-        labels.extend(self.container.text_labels());
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.draw_children {
-            return Vec::new();
-        }
-        let mut labels = self.header.text_labels_with_bounds(ctx);
-        labels.extend(self.container.text_labels_with_bounds(ctx));
-        labels
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        if !self.draw_children {
-            return Vec::new();
-        }
-        let mut labels = self.header.text_labels_with_font_and_bounds(ctx);
-        labels.extend(self.container.text_labels_with_font_and_bounds(ctx));
-        labels
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.draw_children {
-            return Vec::new();
-        }
-        let mut items = self.header.get_text_items();
-        items.extend(self.container.get_text_items());
-        items
-    }
-
     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();
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 8c9281c..f342a2d 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -913,242 +913,6 @@ impl Element for TreeList {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let f32_to_rgb = |c: [f32; 4]| -> [u8; 3] {
-            [
-                (crate::color::linear_to_srgb(c[0]) * 255.0).round() as u8,
-                (crate::color::linear_to_srgb(c[1]) * 255.0).round() as u8,
-                (crate::color::linear_to_srgb(c[2]) * 255.0).round() as u8,
-            ]
-        };
-
-        let (_, tree_font_size) = crate::layout::tree_font_parsed();
-        let header_font_size = (tree_font_size - 1.0).max(8.0);
-
-        let mut labels = Vec::new();
-        let list_left = self.scroll_box.base.x;
-        let list_top = self.scroll_box.viewport_y;
-        let list_bottom = self.scroll_box.viewport_y + self.scroll_box.viewport_h;
-
-        let search_margin_y = 6.0;
-        let search_h = 26.0;
-        let offset_y = search_h + 2.0 * search_margin_y;
-
-        labels.push(TextLabel {
-            text: "Key".to_string(),
-            x: list_left + 8.0,
-            y: self.base.y + offset_y + 6.0,
-            font_size: header_font_size,
-            color: [200, 200, 210],
-        });
-        labels.push(TextLabel {
-            text: "Type".to_string(),
-            x: list_left + 180.0 + 8.0,
-            y: self.base.y + offset_y + 6.0,
-            font_size: header_font_size,
-            color: [200, 200, 210],
-        });
-        labels.push(TextLabel {
-            text: "Value".to_string(),
-            x: list_left + 235.0 + 8.0,
-            y: self.base.y + offset_y + 6.0,
-            font_size: header_font_size,
-            color: [200, 200, 210],
-        });
-
-        for (i, item) in self.items.iter().enumerate() {
-            let row_y = list_top + i as f32 * self.item_height - self.scroll_box.scroll_y;
-            if row_y + self.item_height < list_top || row_y > list_bottom {
-                continue;
-            }
-
-            match item {
-                TreeElement::Section { name, indent, collapsed, .. } => {
-                    if self.editing_key_idx != Some(i) {
-                        let display_text = format!("{} {}", if *collapsed { "▶" } else { "▼" }, name);
-                        labels.push(TextLabel {
-                            text: display_text,
-                            x: list_left + 8.0 + *indent as f32 * 12.0,
-                            y: row_y + 6.0,
-                            font_size: tree_font_size,
-                            color: f32_to_rgb(crate::color::tree_section_text_color()),
-                        });
-                    }
-                }
-                TreeElement::Leaf { name, indent, val, original_idx, .. } => {
-                    let val_str = serde_json::to_string(val).unwrap_or_default();
-                    let display_val = if val_str.len() > 18 {
-                        format!("{}...", &val_str[..15])
-                    } else {
-                        val_str
-                    };
-
-                    let color = if Some(*original_idx) == self.selected_key_idx {
-                        f32_to_rgb(crate::color::tree_leaf_text_selected_color())
-                    } else {
-                        f32_to_rgb(crate::color::tree_leaf_text_color())
-                    };
-
-                    if self.editing_key_idx != Some(i) {
-                        labels.push(TextLabel {
-                            text: name.clone(),
-                            x: list_left + 8.0 + *indent as f32 * 12.0,
-                            y: row_y + 6.0,
-                            font_size: tree_font_size,
-                            color,
-                        });
-                    }
-
-                    let val_ty = match val {
-                        serde_json::Value::Bool(_) => Some("bool"),
-                        serde_json::Value::Number(num) => {
-                            if num.is_f64() {
-                                Some("f64")
-                            } else {
-                                Some("i64")
-                            }
-                        }
-                        serde_json::Value::String(s) => {
-                            if s.starts_with('#') {
-                                let s_clean = s.trim_start_matches('#');
-                                if s_clean.len() == 8 {
-                                    Some("rgba")
-                                } else {
-                                    Some("rgb")
-                                }
-                            } else if name == "key" || name == "keybind" || name == "shortcut" || name == "open_search" || name == "close_search" || name == "delete" || name.ends_with("_key") || name.ends_with(".key") || name.ends_with(".keybind") || name.ends_with(".shortcut") || name.ends_with(".open_search") || name.ends_with(".close_search") || name.ends_with("_delete") || name.ends_with(".delete") {
-                                Some("keybind")
-                            } else if name == "font" || name.ends_with("_font") || name.ends_with(".font") {
-                                Some("font")
-                            } else {
-                                None
-                            }
-                        }
-                        _ => None,
-                    };
-                    let mut display_ty = val_ty.map(|s| s.to_string());
-                    if let Some(Some(ref anno)) = self.annotations.get(*original_idx) {
-                        if anno.starts_with("menu:") {
-                            display_ty = Some("menu".to_string());
-                        } else if anno == "button" || anno.starts_with("button:") {
-                            display_ty = Some("button".to_string());
-                        } else {
-                            display_ty = Some(anno.clone());
-                        }
-                    } else if display_ty.is_none() {
-                        if let serde_json::Value::String(_) = val {
-                            display_ty = Some("string".to_string());
-                        }
-                    }
-
-                    if let Some(ty) = display_ty {
-                        let ty_text = format!("({})", ty);
-                        labels.push(TextLabel {
-                            text: ty_text,
-                            x: list_left + 190.0,
-                            y: row_y + 6.0,
-                            font_size: tree_font_size,
-                            color: f32_to_rgb(crate::color::tree_type_text_color()),
-                        });
-                    }
-
-                    if Some(*original_idx) != self.selected_key_idx {
-                        let mut is_button = false;
-                        if let Some(Some(ref anno)) = self.annotations.get(*original_idx) {
-                            if anno == "button" || anno.starts_with("button:") {
-                                is_button = true;
-                            }
-                        }
-
-                        let is_color = if let serde_json::Value::String(s) = val {
-                            s.starts_with('#')
-                        } else {
-                            false
-                        };
-                        
-                        let label_x = if is_color {
-                            list_left + 267.0
-                        } else {
-                            list_left + 245.0
-                        };
-
-                        if is_button {
-                            labels.push(TextLabel {
-                                text: display_val,
-                                x: list_left + 245.0 + 8.0,
-                                y: row_y + 6.0,
-                                font_size: tree_font_size,
-                                color: [240, 240, 245],
-                            });
-                        } else {
-                            labels.push(TextLabel {
-                                text: display_val,
-                                x: label_x,
-                                y: row_y + 6.0,
-                                font_size: tree_font_size,
-                                color: f32_to_rgb(crate::color::tree_value_text_color()),
-                            });
-                        }
-                    }
-                }
-            }
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let (x, y, w, _h) = self.rect();
-        
-        let search_margin_y = 6.0;
-        let search_h = 26.0;
-        let offset_y = search_h + 2.0 * search_margin_y;
-        let header_h = 26.0;
-
-        let list_bounds = Some([self.scroll_box.base.x, self.scroll_box.viewport_y, self.scroll_box.base.x + self.scroll_box.base.w, self.scroll_box.viewport_y + self.scroll_box.viewport_h]);
-        let header_bounds = Some([x, y + offset_y, x + w, y + offset_y + header_h]);
-        
-        let mut labels = self.text_labels().into_iter().enumerate().map(|(idx, l)| {
-            let b = if idx < 3 {
-                header_bounds
-            } else {
-                list_bounds
-            };
-            (l, b)
-        }).collect::<Vec<_>>();
-
-        labels.extend(self.search_box.text_labels_with_bounds(ctx));
-        labels
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let font = self.widget_font();
-        let (x, y, w, _h) = self.rect();
-        
-        let search_margin_y = 6.0;
-        let search_h = 26.0;
-        let offset_y = search_h + 2.0 * search_margin_y;
-        let header_h = 26.0;
-
-        let list_bounds = Some([self.scroll_box.base.x, self.scroll_box.viewport_y, self.scroll_box.base.x + self.scroll_box.base.w, self.scroll_box.viewport_y + self.scroll_box.viewport_h]);
-        let header_bounds = Some([x, y + offset_y, x + w, y + offset_y + header_h]);
-        
-        let mut labels = self.text_labels().into_iter().enumerate().map(|(idx, l)| {
-            let b = if idx < 3 {
-                header_bounds
-            } else {
-                list_bounds
-            };
-            (l, font.clone(), b)
-        }).collect::<Vec<_>>();
-
-        labels.extend(self.search_box.text_labels_with_font_and_bounds(ctx));
-        labels.extend(self.add_key_btn.text_labels_with_font_and_bounds(ctx));
-        if self.add_key_popover_open {
-            labels.extend(self.add_key_popover_box.text_labels_with_font_and_bounds(ctx));
-        }
-        labels
-    }
-
     fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
         self.parent
     }
@@ -1219,7 +983,7 @@ impl Element for TreeList {
         for (cx, cy, r, c) in self.extra_circles() {
             pc.circle(cx, cy, r, c);
         }
-        for (tl, font, bounds) in self.text_labels_with_font_and_bounds(ui) {
+        for (tl, font, bounds) in self.subtree_fonted_labels(ui) {
             pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, bounds);
         }
     }
@@ -1688,7 +1452,7 @@ mod tests {
             ("input.accel_profile".to_string(), serde_json::Value::String("flat".to_string()))
         ]);
         
-        let labels = tree_list.text_labels();
+        let labels = tree_list.own_labels();
         for label in &labels {
             println!("TEST LABEL: {:?}", label);
         }
@@ -1700,7 +1464,7 @@ mod tests {
     #[test]
     fn test_treelist_headers() {
         let tree_list = TreeList::new();
-        let labels = tree_list.text_labels();
+        let labels = tree_list.own_labels();
         assert!(labels.iter().any(|l| l.text == "Key"), "Should have Key header!");
         assert!(labels.iter().any(|l| l.text == "Type"), "Should have Type header!");
         assert!(labels.iter().any(|l| l.text == "Value"), "Should have Value header!");
@@ -1808,3 +1572,217 @@ mod tests {
         assert_eq!(req, Some(("style.control.dropdown.color".to_string(), "style.control.dropdown.bg_color".to_string())));
     }
 }
+
+impl TreeList {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
+        let f32_to_rgb = |c: [f32; 4]| -> [u8; 3] {
+            [
+                (crate::color::linear_to_srgb(c[0]) * 255.0).round() as u8,
+                (crate::color::linear_to_srgb(c[1]) * 255.0).round() as u8,
+                (crate::color::linear_to_srgb(c[2]) * 255.0).round() as u8,
+            ]
+        };
+
+        let (_, tree_font_size) = crate::layout::tree_font_parsed();
+        let header_font_size = (tree_font_size - 1.0).max(8.0);
+
+        let mut labels = Vec::new();
+        let list_left = self.scroll_box.base.x;
+        let list_top = self.scroll_box.viewport_y;
+        let list_bottom = self.scroll_box.viewport_y + self.scroll_box.viewport_h;
+
+        let search_margin_y = 6.0;
+        let search_h = 26.0;
+        let offset_y = search_h + 2.0 * search_margin_y;
+
+        labels.push(TextLabel {
+            text: "Key".to_string(),
+            x: list_left + 8.0,
+            y: self.base.y + offset_y + 6.0,
+            font_size: header_font_size,
+            color: [200, 200, 210],
+        });
+        labels.push(TextLabel {
+            text: "Type".to_string(),
+            x: list_left + 180.0 + 8.0,
+            y: self.base.y + offset_y + 6.0,
+            font_size: header_font_size,
+            color: [200, 200, 210],
+        });
+        labels.push(TextLabel {
+            text: "Value".to_string(),
+            x: list_left + 235.0 + 8.0,
+            y: self.base.y + offset_y + 6.0,
+            font_size: header_font_size,
+            color: [200, 200, 210],
+        });
+
+        for (i, item) in self.items.iter().enumerate() {
+            let row_y = list_top + i as f32 * self.item_height - self.scroll_box.scroll_y;
+            if row_y + self.item_height < list_top || row_y > list_bottom {
+                continue;
+            }
+
+            match item {
+                TreeElement::Section { name, indent, collapsed, .. } => {
+                    if self.editing_key_idx != Some(i) {
+                        let display_text = format!("{} {}", if *collapsed { "▶" } else { "▼" }, name);
+                        labels.push(TextLabel {
+                            text: display_text,
+                            x: list_left + 8.0 + *indent as f32 * 12.0,
+                            y: row_y + 6.0,
+                            font_size: tree_font_size,
+                            color: f32_to_rgb(crate::color::tree_section_text_color()),
+                        });
+                    }
+                }
+                TreeElement::Leaf { name, indent, val, original_idx, .. } => {
+                    let val_str = serde_json::to_string(val).unwrap_or_default();
+                    let display_val = if val_str.len() > 18 {
+                        format!("{}...", &val_str[..15])
+                    } else {
+                        val_str
+                    };
+
+                    let color = if Some(*original_idx) == self.selected_key_idx {
+                        f32_to_rgb(crate::color::tree_leaf_text_selected_color())
+                    } else {
+                        f32_to_rgb(crate::color::tree_leaf_text_color())
+                    };
+
+                    if self.editing_key_idx != Some(i) {
+                        labels.push(TextLabel {
+                            text: name.clone(),
+                            x: list_left + 8.0 + *indent as f32 * 12.0,
+                            y: row_y + 6.0,
+                            font_size: tree_font_size,
+                            color,
+                        });
+                    }
+
+                    let val_ty = match val {
+                        serde_json::Value::Bool(_) => Some("bool"),
+                        serde_json::Value::Number(num) => {
+                            if num.is_f64() {
+                                Some("f64")
+                            } else {
+                                Some("i64")
+                            }
+                        }
+                        serde_json::Value::String(s) => {
+                            if s.starts_with('#') {
+                                let s_clean = s.trim_start_matches('#');
+                                if s_clean.len() == 8 {
+                                    Some("rgba")
+                                } else {
+                                    Some("rgb")
+                                }
+                            } else if name == "key" || name == "keybind" || name == "shortcut" || name == "open_search" || name == "close_search" || name == "delete" || name.ends_with("_key") || name.ends_with(".key") || name.ends_with(".keybind") || name.ends_with(".shortcut") || name.ends_with(".open_search") || name.ends_with(".close_search") || name.ends_with("_delete") || name.ends_with(".delete") {
+                                Some("keybind")
+                            } else if name == "font" || name.ends_with("_font") || name.ends_with(".font") {
+                                Some("font")
+                            } else {
+                                None
+                            }
+                        }
+                        _ => None,
+                    };
+                    let mut display_ty = val_ty.map(|s| s.to_string());
+                    if let Some(Some(ref anno)) = self.annotations.get(*original_idx) {
+                        if anno.starts_with("menu:") {
+                            display_ty = Some("menu".to_string());
+                        } else if anno == "button" || anno.starts_with("button:") {
+                            display_ty = Some("button".to_string());
+                        } else {
+                            display_ty = Some(anno.clone());
+                        }
+                    } else if display_ty.is_none() {
+                        if let serde_json::Value::String(_) = val {
+                            display_ty = Some("string".to_string());
+                        }
+                    }
+
+                    if let Some(ty) = display_ty {
+                        let ty_text = format!("({})", ty);
+                        labels.push(TextLabel {
+                            text: ty_text,
+                            x: list_left + 190.0,
+                            y: row_y + 6.0,
+                            font_size: tree_font_size,
+                            color: f32_to_rgb(crate::color::tree_type_text_color()),
+                        });
+                    }
+
+                    if Some(*original_idx) != self.selected_key_idx {
+                        let mut is_button = false;
+                        if let Some(Some(ref anno)) = self.annotations.get(*original_idx) {
+                            if anno == "button" || anno.starts_with("button:") {
+                                is_button = true;
+                            }
+                        }
+
+                        let is_color = if let serde_json::Value::String(s) = val {
+                            s.starts_with('#')
+                        } else {
+                            false
+                        };
+                        
+                        let label_x = if is_color {
+                            list_left + 267.0
+                        } else {
+                            list_left + 245.0
+                        };
+
+                        if is_button {
+                            labels.push(TextLabel {
+                                text: display_val,
+                                x: list_left + 245.0 + 8.0,
+                                y: row_y + 6.0,
+                                font_size: tree_font_size,
+                                color: [240, 240, 245],
+                            });
+                        } else {
+                            labels.push(TextLabel {
+                                text: display_val,
+                                x: label_x,
+                                y: row_y + 6.0,
+                                font_size: tree_font_size,
+                                color: f32_to_rgb(crate::color::tree_value_text_color()),
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        labels
+    }
+
+    pub(crate) fn subtree_fonted_labels(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+        let font = self.widget_font();
+        let (x, y, w, _h) = self.rect();
+        
+        let search_margin_y = 6.0;
+        let search_h = 26.0;
+        let offset_y = search_h + 2.0 * search_margin_y;
+        let header_h = 26.0;
+
+        let list_bounds = Some([self.scroll_box.base.x, self.scroll_box.viewport_y, self.scroll_box.base.x + self.scroll_box.base.w, self.scroll_box.viewport_y + self.scroll_box.viewport_h]);
+        let header_bounds = Some([x, y + offset_y, x + w, y + offset_y + header_h]);
+        
+        let mut labels = self.own_labels().into_iter().enumerate().map(|(idx, l)| {
+            let b = if idx < 3 {
+                header_bounds
+            } else {
+                list_bounds
+            };
+            (l, font.clone(), b)
+        }).collect::<Vec<_>>();
+
+        labels.extend(self.search_box.own_labels_with_font_and_bounds(ctx));
+        labels.extend(self.add_key_btn.own_labels_with_font_and_bounds(ctx));
+        if self.add_key_popover_open {
+            labels.extend(self.add_key_popover_box.own_labels_with_font_and_bounds(ctx));
+        }
+        labels
+    }
+}
diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index ba43333..fc8f3f3 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -97,7 +97,7 @@ mod tests {
         let mut l = Label::new("CPU: 3%").with_font_size(13.0).with_color([1, 2, 3]);
         Element::set_rect(&mut l, 10.0, 20.0, 100.0, 16.0);
 
-        let labels = Element::text_labels(&l);
+        let labels = l.own_text_labels();
         assert_eq!(labels.len(), 1);
         assert_eq!(labels[0].text, "CPU: 3%");
         assert_eq!(labels[0].x, 10.0);
@@ -105,7 +105,7 @@ mod tests {
         assert_eq!(labels[0].color, [1, 2, 3]);
 
         Element::set_text(&mut l, "CPU: 99%");
-        assert_eq!(Element::text_labels(&l)[0].text, "CPU: 99%", "set_text reaches the paint source");
+        assert_eq!(l.own_text_labels()[0].text, "CPU: 99%", "set_text reaches the paint source");
 
         let size = Element::intrinsic_size(&l).unwrap();
         assert!(size.width > 0.0);
@@ -128,20 +128,21 @@ impl SectionHeader {
 
 impl Element for SectionHeader {
     crate::impl_widget_base!(SectionHeader);
+
+    // Leaf legacy widget: own fonted labels via paint_self (the default no longer
+    // drains the text getters).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(
+            self, ui, ctx,
+            crate::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
+        );
+    }
     fn blocks_backplate_drag(&self) -> bool { false }
     fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
         vec![(self.base.x + 8.0, self.base.y + 22.0, self.base.w - 16.0, 1.0, [0.18, 0.18, 0.27, 1.0])]
     }
-    fn text_labels(&self) -> Vec<TextLabel> {
-        vec![TextLabel {
-            text: self.base.label.clone().unwrap_or_default(),
-            x: self.base.x + 12.0,
-            y: self.base.y,
-            font_size: 14.0,
-            color: [212, 212, 212],
-        }]
-    }
+
 }
 
 // Styled label builder with optional strikethrough
@@ -297,3 +298,15 @@ impl StyledLabel {
         }
     }
 }
+
+impl SectionHeader {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
+        vec![TextLabel {
+            text: self.base.label.clone().unwrap_or_default(),
+            x: self.base.x + 12.0,
+            y: self.base.y,
+            font_size: 14.0,
+            color: [212, 212, 212],
+        }]
+    }
+}
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 44432c3..a40a538 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -207,22 +207,6 @@ impl Paint for StatusBar {
         }
     }
 
-    fn text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if let Some(ref text_buf) = self.text_buf {
-            let offset_x = self.text_offset_x.unwrap_or(12.0);
-            let c = self.get_actual_text_color();
-            let color = glyphon::Color::rgb(
-                (c[0] * 255.0) as u8,
-                (c[1] * 255.0) as u8,
-                (c[2] * 255.0) as u8,
-            );
-            let size = self.statusbar_font_size();
-            let text_y = crate::layout::align_text_y(self.rect.y, self.rect.height, size, 0.0);
-            vec![(text_buf, self.rect.x + offset_x, text_y, color)]
-        } else {
-            Vec::new()
-        }
-    }
 }
 
 impl Input for StatusBar {
@@ -236,26 +220,24 @@ mod tests {
     use super::*;
     use crate::widget::Element;
 
-    /// The manual-host path cce-status-interface drives by hand: `set_text` drops the shaped
-    /// buffer, `prepare_text` rebuilds it, `get_text_items` serves it (the new
-    /// `Paint::text_items` hook) at the bar's rect.
+    /// The shaped-buffer lifecycle behind the old manual-host path: `set_text` drops the
+    /// buffer, `prepare_text` rebuilds it. (The `get_text_items` getter that served it is
+    /// deleted; the bar's rendered text is the `Paint::paint` prim.)
     #[test]
     fn manual_host_text_pipeline() {
         let mut fs = glyphon::FontSystem::new();
         let mut bar = StatusBar::new().with_text("hello").with_text_offset_x(15.0);
         Element::set_rect(&mut bar, 0.0, 570.0, 800.0, 30.0);
 
-        assert!(Element::get_text_items(&bar).is_empty(), "no buffer before prepare_text");
+        assert!(bar.text_buf.is_none(), "no buffer before prepare_text");
         Element::prepare_text(&mut bar, &mut fs);
-        let items = Element::get_text_items(&bar);
-        assert_eq!(items.len(), 1, "one shaped buffer");
-        assert_eq!(items[0].1, 15.0, "x = rect.x + text_offset_x");
+        assert!(bar.text_buf.is_some(), "one shaped buffer");
 
         // set_text drops the stale buffer; prepare_text reshapes.
         Element::set_text(&mut bar, "world");
-        assert!(Element::get_text_items(&bar).is_empty(), "buffer dropped on text change");
+        assert!(bar.text_buf.is_none(), "buffer dropped on text change");
         Element::prepare_text(&mut bar, &mut fs);
-        assert_eq!(Element::get_text_items(&bar).len(), 1);
+        assert!(bar.text_buf.is_some());
         assert_eq!(bar.text, "world");
 
         // Parentless: cornerless plain bg through the plain-quad bridge, at STATUS_BG.
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 7bb92c0..d104222 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -458,7 +458,7 @@ mod tests {
             assert!(rounded.is_empty() && !plain.is_empty(), "square config -> plain path only");
         }
 
-        let labels = Element::text_labels(&b);
+        let labels = b.own_text_labels();
         assert_eq!(labels.len(), 1);
         assert_eq!(labels[0].text, "Go");
         let est = b.label_width("Go");
diff --git a/src/widget/input/button_strip.rs b/src/widget/input/button_strip.rs
index 4bdddea..9e89a06 100644
--- a/src/widget/input/button_strip.rs
+++ b/src/widget/input/button_strip.rs
@@ -407,7 +407,58 @@ impl Element for ButtonStrip {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
+    // Leaf legacy widget: own fonted labels via paint_self (the trait text getters
+    // are deleted).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(
+            self, ui, ctx,
+            crate::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
+        );
+    }
+
+    fn keyboard_input(&mut self, event: &KeyEvent, _ctx: &mut UiContext) -> bool {
+        if event.state != ElementState::Pressed { return false; }
+        if self.buttons.is_empty() { return false; }
+
+        let current = self.selected.unwrap_or(0);
+        let next;
+
+        match event.logical_key {
+            Key::Named(NamedKey::ArrowLeft) | Key::Named(NamedKey::ArrowUp) => {
+                if current > 0 {
+                    next = current - 1;
+                } else {
+                    next = self.buttons.len() - 1;
+                }
+            }
+            Key::Named(NamedKey::ArrowRight) | Key::Named(NamedKey::ArrowDown) => {
+                if current + 1 < self.buttons.len() {
+                    next = current + 1;
+                } else {
+                    next = 0;
+                }
+            }
+            _ => return false,
+        }
+
+        if Some(next) != self.selected {
+            self.selected = Some(next);
+            self.just_clicked = Some(next);
+            self.generate_rotated_labels();
+            return true;
+        }
+        false
+    }
+
+    fn widget_font(&self) -> Option<String> {
+        Some(self.current_font())
+    }
+}
+
+impl Control for ButtonStrip {}
+
+impl ButtonStrip {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
         let mut labels = Vec::new();
         let font_info = self.current_font_parsed();
         let font_fam = font_info.0;
@@ -454,44 +505,4 @@ impl Element for ButtonStrip {
         }
         labels
     }
-
-    fn keyboard_input(&mut self, event: &KeyEvent, _ctx: &mut UiContext) -> bool {
-        if event.state != ElementState::Pressed { return false; }
-        if self.buttons.is_empty() { return false; }
-
-        let current = self.selected.unwrap_or(0);
-        let next;
-
-        match event.logical_key {
-            Key::Named(NamedKey::ArrowLeft) | Key::Named(NamedKey::ArrowUp) => {
-                if current > 0 {
-                    next = current - 1;
-                } else {
-                    next = self.buttons.len() - 1;
-                }
-            }
-            Key::Named(NamedKey::ArrowRight) | Key::Named(NamedKey::ArrowDown) => {
-                if current + 1 < self.buttons.len() {
-                    next = current + 1;
-                } else {
-                    next = 0;
-                }
-            }
-            _ => return false,
-        }
-
-        if Some(next) != self.selected {
-            self.selected = Some(next);
-            self.just_clicked = Some(next);
-            self.generate_rotated_labels();
-            return true;
-        }
-        false
-    }
-
-    fn widget_font(&self) -> Option<String> {
-        Some(self.current_font())
-    }
 }
-
-impl Control for ButtonStrip {}
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 52ac0f2..d5d5aa0 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -520,7 +520,7 @@ mod tests {
         assert_eq!(quads[0].4, colors::checkbox_hover());
 
         // Label text comes through the prim-derived text bridge at the legacy position.
-        let labels = Element::text_labels(&cb);
+        let labels = cb.own_text_labels();
         assert_eq!(labels.len(), 1);
         assert_eq!(labels[0].text, "Enable");
         assert_eq!(labels[0].x, 8.0);
@@ -563,7 +563,7 @@ mod tests {
         let mut t = Toggle::new();
         Element::set_rect(&mut t, 0.0, 0.0, 60.0, 30.0);
         t.set_label("ON"); // the network.rs pattern: live label updates through Deref
-        let labels = Element::text_labels(&t);
+        let labels = t.own_text_labels();
         assert_eq!(labels.len(), 1);
         assert_eq!(labels[0].text, "ON");
     }
diff --git a/src/widget/input/color_selector.rs b/src/widget/input/color_selector.rs
index 0435e9e..1de877d 100644
--- a/src/widget/input/color_selector.rs
+++ b/src/widget/input/color_selector.rs
@@ -113,6 +113,15 @@ impl ColorSelector {
 impl Element for ColorSelector {
     crate::impl_widget_base!(ColorSelector);
 
+    // Leaf legacy widget: own fonted labels via paint_self (the default no longer
+    // drains the text getters).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(
+            self, ui, ctx,
+            crate::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
+        );
+    }
+
     fn get_value_string(&self) -> Option<String> {
         if self.with_alpha {
             Some(format!("#{:02x}{:02x}{:02x}{:02x}", self.color[0], self.color[1], self.color[2], self.alpha))
@@ -509,23 +518,7 @@ impl Element for ColorSelector {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some(lbl) = self.control_label() {
-            labels.push(lbl);
-        }
-        let hex = if self.editing { self.edit_buffer.clone() } else { self.get_value_string().unwrap() };
-        let top = self.base.label_offset();
-        let _visual_h = self.base.h - top;
-        labels.push(TextLabel {
-            text: hex,
-            x: self.base.x + 4.0,
-            y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        labels
-    }
+
 }
 
 impl Drop for ColorSelector {
@@ -754,3 +747,22 @@ mod tests {
     }
 }
 
+impl ColorSelector {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
+        let mut labels = Vec::new();
+        if let Some(lbl) = self.control_label() {
+            labels.push(lbl);
+        }
+        let hex = if self.editing { self.edit_buffer.clone() } else { self.get_value_string().unwrap() };
+        let top = self.base.label_offset();
+        let _visual_h = self.base.h - top;
+        labels.push(TextLabel {
+            text: hex,
+            x: self.base.x + 4.0,
+            y: crate::layout::align_text_y(self.base.y, self.base.h, 12.0, top),
+            font_size: 12.0,
+            color: [0xcc, 0xcc, 0xd4],
+        });
+        labels
+    }
+}
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index fa77401..140ca8b 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -977,7 +977,7 @@ mod tests {
         let mut dd = Dropdown::new(options, 0);
         dd.set_rect(10.0, 10.0, 100.0, 24.0); // very narrow dropdown
 
-        let labels = dd.text_labels();
+        let labels = dd.own_text_labels();
         // Labels are individual characters of selected_text, then the ▼ arrow (prim order).
         assert!(labels.len() > 2);
 
diff --git a/src/widget/input/font_selector.rs b/src/widget/input/font_selector.rs
index 28b87e0..b7a4d7d 100644
--- a/src/widget/input/font_selector.rs
+++ b/src/widget/input/font_selector.rs
@@ -46,6 +46,12 @@ impl FontSelector {
 impl Element for FontSelector {
     crate::impl_widget_base!(FontSelector);
 
+    // Leaf legacy widget: own fonted labels via paint_self (the default no longer
+    // drains the text getters).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(self, ui, ctx, self.own_fonted_labels());
+    }
+
     fn preferred_height(&self) -> Option<f32> {
         Some(crate::layout::font_selector_height())
     }
@@ -146,7 +152,33 @@ impl Element for FontSelector {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        let r = crate::layout::font_selector_corner_radius();
+        if r > 0.0 {
+            (true, true, true, true)
+        } else {
+            (false, false, false, false)
+        }
+    }
+
+    fn corner_radius(&self) -> f32 {
+        crate::layout::font_selector_corner_radius()
+    }
+}
+
+impl Drop for FontSelector {
+    fn drop(&mut self) {
+        clear_widget_references(self);
+    }
+}
+
+unsafe impl Send for FontSelector {}
+unsafe impl Sync for FontSelector {}
+
+impl Control for FontSelector {}
+
+impl FontSelector {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
         let mut labels = Vec::new();
         let top = self.base.label_offset();
         let _visual_h = self.base.h - top;
@@ -227,13 +259,13 @@ impl Element for FontSelector {
         labels
     }
 
-    fn text_labels_with_font_and_bounds(&self, _ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+    pub(crate) fn own_fonted_labels(&self) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
         let font = self.widget_font();
         let top = self.base.label_offset();
         let clip_right = self.base.x + self.base.w - 24.0;
         let bounds = Some([self.base.x, self.base.y + top, clip_right, self.base.y + self.base.h]);
         
-        let labels = self.text_labels();
+        let labels = self.own_labels();
         let count = labels.len();
         labels.into_iter().enumerate().map(|(idx, l)| {
             let has_control = self.control_label().is_some();
@@ -249,28 +281,4 @@ impl Element for FontSelector {
             }
         }).collect()
     }
-
-    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
-        let r = crate::layout::font_selector_corner_radius();
-        if r > 0.0 {
-            (true, true, true, true)
-        } else {
-            (false, false, false, false)
-        }
-    }
-
-    fn corner_radius(&self) -> f32 {
-        crate::layout::font_selector_corner_radius()
-    }
 }
-
-impl Drop for FontSelector {
-    fn drop(&mut self) {
-        clear_widget_references(self);
-    }
-}
-
-unsafe impl Send for FontSelector {}
-unsafe impl Sync for FontSelector {}
-
-impl Control for FontSelector {}
diff --git a/src/widget/input/keybind_recorder.rs b/src/widget/input/keybind_recorder.rs
index a5dc5a4..d12e98c 100644
--- a/src/widget/input/keybind_recorder.rs
+++ b/src/widget/input/keybind_recorder.rs
@@ -41,6 +41,15 @@ impl KeybindRecorder {
 impl Element for KeybindRecorder {
     crate::impl_widget_base!(KeybindRecorder);
 
+    // Leaf legacy widget: own fonted labels via paint_self (the default no longer
+    // drains the text getters).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(
+            self, ui, ctx,
+            crate::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
+        );
+    }
+
     fn preferred_height(&self) -> Option<f32> {
         Some(crate::layout::textbox_height())
     }
@@ -200,7 +209,17 @@ impl Element for KeybindRecorder {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
+
+
+    fn unfocus(&mut self) {
+        self.recording = false;
+    }
+}
+
+impl Control for KeybindRecorder {}
+
+impl KeybindRecorder {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
         let mut labels = Vec::new();
         let top = self.base.label_offset();
         
@@ -229,9 +248,4 @@ impl Element for KeybindRecorder {
         labels
     }
 
-    fn unfocus(&mut self) {
-        self.recording = false;
-    }
 }
-
-impl Control for KeybindRecorder {}
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index 723a3a1..b207eba 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -384,27 +384,6 @@ impl Element for ColorRamp {
         changed
     }
     
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        if self.selected_key_idx.is_some() {
-            labels.extend(self.r_slider.text_labels_with_font_and_bounds(ctx));
-            labels.extend(self.g_slider.text_labels_with_font_and_bounds(ctx));
-            labels.extend(self.b_slider.text_labels_with_font_and_bounds(ctx));
-            labels.extend(self.del_button.text_labels_with_font_and_bounds(ctx));
-        }
-        labels
-    }
-    
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut items = Vec::new();
-        if self.selected_key_idx.is_some() {
-            items.extend(self.r_slider.get_text_items());
-            items.extend(self.g_slider.get_text_items());
-            items.extend(self.b_slider.get_text_items());
-            items.extend(self.del_button.get_text_items());
-        }
-        items
-    }
 }
 
 impl Drop for ColorRamp {
@@ -991,22 +970,6 @@ impl Element for Ramp {
         self.line_type_dropdown.render_popover(pc);
     }
     
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut labels: Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> = self
-            .own_control_labels()
-            .into_iter()
-            .map(|(l, f)| (l, f, None))
-            .collect();
-
-        labels.extend(self.preset_dropdown.text_labels_with_font_and_bounds(ctx));
-        labels.extend(self.line_type_dropdown.text_labels_with_font_and_bounds(ctx));
-        if self.selected_key_idx.is_some() {
-            labels.extend(self.val_slider.text_labels_with_font_and_bounds(ctx));
-            labels.extend(self.del_button.text_labels_with_font_and_bounds(ctx));
-        }
-        labels
-    }
-
     /// The walk drops a container's own text (assumed child aggregate) — Ramp's control
     /// labels are its OWN; the children (dropdowns/slider/button) are painted by the
     /// walk's descent, so only the own labels are emitted here, over the default's
@@ -1042,16 +1005,6 @@ impl Element for Ramp {
         }
     }
     
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut items = Vec::new();
-        items.extend(self.preset_dropdown.get_text_items());
-        items.extend(self.line_type_dropdown.get_text_items());
-        if self.selected_key_idx.is_some() {
-            items.extend(self.val_slider.get_text_items());
-            items.extend(self.del_button.get_text_items());
-        }
-        items
-    }
 }
 
 impl Ramp {
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index c379c26..7a66b3b 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -747,7 +747,7 @@ fn probe_slider_bridge() {
     eprintln!("rect         = {:?}", Element::rect(&sl));
     eprintln!("extra_quads  = {:?}", Element::extra_quads(&sl));
     eprintln!("rounded      = {:?}", Element::all_rounded_quads(&sl, &ctx));
-    eprintln!("labels       = {:?}", Element::text_labels(&sl).iter().map(|l| l.text.clone()).collect::<Vec<_>>());
+    eprintln!("labels       = {:?}", sl.own_text_labels().iter().map(|l| l.text.clone()).collect::<Vec<_>>());
 }
 
     /// Slider press-on-track begins a drag through the routed path; wheel adjusts the value
diff --git a/src/widget/input/trackpad.rs b/src/widget/input/trackpad.rs
index 734fa39..7c6372e 100644
--- a/src/widget/input/trackpad.rs
+++ b/src/widget/input/trackpad.rs
@@ -46,6 +46,15 @@ impl Trackpad {
 impl Element for Trackpad {
     crate::impl_widget_base!(Trackpad);
 
+    // Leaf legacy widget: own fonted labels via paint_self (the default no longer
+    // drains the text getters).
+    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
+        crate::scene::painter::paint_legacy_leaf(
+            self, ui, ctx,
+            crate::scene::painter::fonted_leaf_labels(self, ui, self.own_labels()),
+        );
+    }
+
     fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
         self.base.x = x;
         self.base.y = y;
@@ -105,37 +114,27 @@ impl Element for Trackpad {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let (rx_rect, y, rw_rect, h) = self.rect();
-        let label_x = self.label_x_offset();
-        let x = rx_rect + label_x;
-        let _w = rw_rect - label_x;
-        let top = self.label_offset();
-        let visual_h = h - top;
-        let mut labels = Vec::new();
-
-        // Render "Touchpad Area" label
-        labels.push(TextLabel {
-            text: "Touchpad Area".to_string(),
-            x: x + 12.0,
-            y: y + top + visual_h - 22.0,
-            font_size: 11.0,
-            color: [0x73, 0x73, 0x8c],
-        });
-
-        // Optional widget-base label on top
-        if let Some(ref label) = self.base.label {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: rx_rect,
-                y,
-                font_size,
-                color: colors::control_label_color_detached_for_state(self.base.hovered, self.base.focused),
-            });
+    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, _ctx: &mut UiContext) -> bool {
+        if button == MouseButton::Left {
+            let (rx_rect, y, rw_rect, h) = self.rect();
+            let label_x = self.label_x_offset();
+            let x = rx_rect + label_x;
+            let w = rw_rect - label_x;
+            let top = self.label_offset();
+            let visual_h = h - top;
+            if px >= x && px <= x + w && py >= y + top && py <= y + top + visual_h {
+                if state == ElementState::Pressed {
+                    let rx = ((px - x) / w).clamp(0.0, 1.0);
+                    let ry = ((py - (y + top)) / visual_h).clamp(0.0, 1.0);
+                    self.fingers = vec![Finger { slot: 0, x: rx, y: ry }];
+                    return true;
+                } else {
+                    self.fingers.clear();
+                    return true;
+                }
+            }
         }
-
-        labels
+        false
     }
 
     fn draggable(&self) -> bool { true }
@@ -175,27 +174,40 @@ impl Element for Trackpad {
     fn drag_end(&mut self) {
         self.fingers.clear();
     }
+}
 
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, _ctx: &mut UiContext) -> bool {
-        if button == MouseButton::Left {
-            let (rx_rect, y, rw_rect, h) = self.rect();
-            let label_x = self.label_x_offset();
-            let x = rx_rect + label_x;
-            let w = rw_rect - label_x;
-            let top = self.label_offset();
-            let visual_h = h - top;
-            if px >= x && px <= x + w && py >= y + top && py <= y + top + visual_h {
-                if state == ElementState::Pressed {
-                    let rx = ((px - x) / w).clamp(0.0, 1.0);
-                    let ry = ((py - (y + top)) / visual_h).clamp(0.0, 1.0);
-                    self.fingers = vec![Finger { slot: 0, x: rx, y: ry }];
-                    return true;
-                } else {
-                    self.fingers.clear();
-                    return true;
-                }
-            }
+impl Trackpad {
+    pub(crate) fn own_labels(&self) -> Vec<TextLabel> {
+        let (rx_rect, y, rw_rect, h) = self.rect();
+        let label_x = self.label_x_offset();
+        let x = rx_rect + label_x;
+        let _w = rw_rect - label_x;
+        let top = self.label_offset();
+        let visual_h = h - top;
+        let mut labels = Vec::new();
+
+        // Render "Touchpad Area" label
+        labels.push(TextLabel {
+            text: "Touchpad Area".to_string(),
+            x: x + 12.0,
+            y: y + top + visual_h - 22.0,
+            font_size: 11.0,
+            color: [0x73, 0x73, 0x8c],
+        });
+
+        // Optional widget-base label on top
+        if let Some(ref label) = self.base.label {
+            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+            labels.push(TextLabel {
+                text: label.clone(),
+                x: rx_rect,
+                y,
+                font_size,
+                color: colors::control_label_color_detached_for_state(self.base.hovered, self.base.focused),
+            });
         }
-        false
+
+        labels
     }
+
 }
diff --git a/src/widget/json_layout.rs b/src/widget/json_layout.rs
index 058f438..dec81b8 100644
--- a/src/widget/json_layout.rs
+++ b/src/widget/json_layout.rs
@@ -380,53 +380,6 @@ impl Element for JsonLayoutWidget {
         quads
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        let active_page = self.active_page;
-
-        for w in &self.widgets {
-            if w.page_idx != active_page {
-                continue;
-            }
-            if w.widget_type == "checkbox" {
-                if let Some(tl) = &w.label_text {
-                    labels.push(tl.clone());
-                }
-            } else {
-                labels.extend(w.widget.text_labels());
-            }
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, _ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        let (bx, by, bw, bh) = self.rect();
-
-        let active_page = self.active_page;
-        let pad_x = 16.0;
-        let content_bounds = Some([bx + pad_x - 4.0, by, bx + bw, by + bh]);
-
-        for w in &self.widgets {
-            if w.page_idx != active_page {
-                continue;
-            }
-            let w_labels = if w.widget_type == "checkbox" {
-                if let Some(tl) = &w.label_text {
-                    vec![tl.clone()]
-                } else {
-                    Vec::new()
-                }
-            } else {
-                w.widget.text_labels()
-            };
-            for l in w_labels {
-                labels.push((l, content_bounds));
-            }
-        }
-        labels
-    }
-
     fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
         let mut changed = false;
 
@@ -637,8 +590,53 @@ impl Element for JsonLayoutWidget {
         for (cx, cy, r, c) in self.extra_circles() {
             pc.circle(cx, cy, r, c);
         }
-        for (tl, bounds) in self.text_labels_with_bounds(ui) {
+        for (tl, bounds) in self.own_labels_with_bounds(ui) {
             pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, None, bounds);
         }
     }
 }
+
+impl JsonLayoutWidget {
+    pub(crate) fn own_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
+        let mut labels = Vec::new();
+        let (bx, by, bw, bh) = self.rect();
+
+        let active_page = self.active_page;
+        let pad_x = 16.0;
+        let content_bounds = Some([bx + pad_x - 4.0, by, bx + bw, by + bh]);
+
+        for w in &self.widgets {
+            if w.page_idx != active_page {
+                continue;
+            }
+            let w_labels = if w.widget_type == "checkbox" {
+                if let Some(tl) = &w.label_text {
+                    vec![tl.clone()]
+                } else {
+                    Vec::new()
+                }
+            } else {
+                // The trait text getters are gone: read the child's text off the paint
+                // walk (same prims, fonts dropped — this consumer shapes with its own
+                // control font, as the legacy getter path did).
+                let mut scratch = crate::scene::paint::PaintCtx::new();
+                crate::scene::painter::append_widget_text(ctx, unsafe { &*w.widget.as_ptr() }, &mut scratch);
+                scratch
+                    .finish()
+                    .items
+                    .into_iter()
+                    .filter_map(|item| match item.prim {
+                        crate::scene::paint::Prim::Text { text, x, y, font_size, color, .. } => {
+                            Some(TextLabel { text, x, y, font_size, color })
+                        }
+                        _ => None,
+                    })
+                    .collect()
+            };
+            for l in w_labels {
+                labels.push((l, content_bounds));
+            }
+        }
+        labels
+    }
+}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 8b148b9..b442da4 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -519,21 +519,11 @@ pub trait Element {
         for (cx, cy, r, c) in self.extra_circles() {
             ctx.circle(cx, cy, r, c);
         }
-        // Text: leaves emit their own labels; containers emit NONE — the legacy container
-        // text_labels overrides (Backplate/Layer/Page/Plate) AGGREGATE their children's
-        // labels, and the walk reaches those children itself, so emitting the aggregate
-        // here would double-draw every descendant's text (the Phase 6d trap). A legacy
-        // container with OWN text overrides paint_self to add it (Plate's label).
-        // Leaves use the FONTED getter (font + scroll-viewport bounds) — the same labels
-        // every legacy tuple consumer served; composites like Ramp only aggregate their
-        // internal field widgets here. This is the ONE remaining trait-getter use in the
-        // paint path; it is deleted together with the getters once every live legacy
-        // widget carries its own paint_self.
-        if self.children(ui).is_empty() {
-            for (tl, font, bounds) in self.text_labels_with_font_and_bounds(ui) {
-                ctx.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, bounds);
-            }
-        }
+        // Text: NONE by default. Every live legacy widget with own text carries a
+        // paint_self override (most via `scene::painter::paint_legacy_leaf` + its own
+        // labels); containers' text_labels aggregates are covered by the walk's descent
+        // (emitting them here would double-draw every descendant's text — the Phase 6d
+        // trap). Migrated widgets go through `Adapted::paint_self`, never this default.
     }
 
     /// Whether the paint walk should clip this widget's children to its rect (scroll/backplate
@@ -577,79 +567,12 @@ pub trait Element {
         }
     }
 
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if let Some(b) = self.base() {
-            if let Some(ref label) = b.label {
-                let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-                let color = colors::control_label_color_detached_for_state(b.hovered, b.focused);
-                if crate::layout::control_label_layout() == "side" {
-                    let label_x = self.label_x_offset();
-                    if label_x > 0.0 {
-                        let y_pos = crate::layout::align_text_y(b.y, b.h, font_size, 0.0);
-                        return vec![TextLabel {
-                            text: label.clone(),
-                            x: b.x + 4.0,
-                            y: y_pos,
-                            font_size,
-                            color,
-                        }];
-                    }
-                }
-                return vec![TextLabel {
-                    text: label.clone(),
-                    x: b.x,
-                    y: b.y,
-                    font_size,
-                    color,
-                }];
-            }
-        }
-        Vec::new()
-    }
-    
-    fn text_labels_with_bounds(&self, _ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        self.text_labels().into_iter().map(|l| (l, None)).collect()
-    }
-    
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let font = self.widget_font();
-        let mut labels = self.text_labels().into_iter().map(|l| (l, font.clone(), None::<[f32; 4]>)).collect::<Vec<_>>();
-
-        let mut curr = self.parent(ctx);
-        let mut scroll_box_bounds = None;
-        while let Some(parent_ptr) = curr {
-            let parent = unsafe { &*parent_ptr };
-            if let Some(scroll_box) = parent.as_any().downcast_ref::<crate::widget::ScrollBox>() {
-                let (sb_x, _, sb_w, _) = scroll_box.rect();
-                let view_min = scroll_box.viewport_y + 4.0;
-                let view_max = scroll_box.viewport_y + scroll_box.viewport_h - 4.0;
-                scroll_box_bounds = Some([sb_x, view_min, sb_x + sb_w, view_max]);
-                break;
-            } else if let Some(list) = parent.as_any().downcast_ref::<crate::widget::List>() {
-                let (sb_x, _, sb_w, _) = list.rect();
-                let view_min = list.scroll_box.viewport_y + 4.0;
-                let view_max = list.scroll_box.viewport_y + list.scroll_box.viewport_h - 4.0;
-                scroll_box_bounds = Some([sb_x, view_min, sb_x + sb_w, view_max]);
-                break;
-            }
-            curr = parent.parent(ctx);
-        }
-
-        if let Some(sb_bounds) = scroll_box_bounds {
-            for item in &mut labels {
-                if let Some(ref mut b) = item.2 {
-                    b[0] = b[0].max(sb_bounds[0]);
-                    b[1] = b[1].max(sb_bounds[1]);
-                    b[2] = b[2].min(sb_bounds[2]);
-                    b[3] = b[3].min(sb_bounds[3]);
-                } else {
-                    item.2 = Some(sb_bounds);
-                }
-            }
-        }
-
-        labels
-    }
+    // The per-widget text getters (text_labels / text_labels_with_bounds /
+    // text_labels_with_font_and_bounds / get_text_items) are GONE: every widget emits
+    // its own text as display-list prims via paint_self (Adapted::paint_self for
+    // migrated widgets; paint_legacy_leaf-based overrides for the legacy leaves). The
+    // deleted default's base-label synthesis lives on in Adapted's base-label fallback,
+    // and its scroll-ancestor clamp in scene::painter::scroll_ancestor_text_bounds.
 
     fn widget_font(&self) -> Option<String> { None }
     fn value(&self) -> i32 { 0 }
@@ -681,7 +604,6 @@ pub trait Element {
         ctx.is_focused_addr(self as *const Self as *const () as usize)
     }
     fn prepare_text(&mut self, _fs: &mut glyphon::FontSystem) {}
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> { Vec::new() }
     fn set_selected(&mut self, _selected: bool) {}
     fn keyboard_input(&mut self, _event: &KeyEvent, _ctx: &mut UiContext) -> bool { false }
 
diff --git a/src/widget/model.rs b/src/widget/model.rs
index efa4835..1aae19a 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -352,16 +352,6 @@ pub trait Paint {
         Vec::new()
     }
 
-    /// Shaped glyphon buffers for the legacy `Element::get_text_items` path — hosts that
-    /// build text off pre-shaped buffers instead of `TextLabel`s (cce-status-interface drives
-    /// its StatusBar by hand: `prepare_text` then `get_text_items` into its own paint).
-    /// Prim-derived text can't serve this (the getter returns borrows of buffers the widget
-    /// owns), so the widget serves them itself; shape the buffers in
-    /// [`prepare_text`](Paint::prepare_text). Default: none — the `Element` default most
-    /// widgets kept.
-    fn text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        Vec::new()
-    }
 }
 
 /// What an event handler may reach beyond its own state — the RFC §3.5 `EventCtx`, grown as
@@ -793,7 +783,7 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
 
     /// This widget's OWN text (prim-derived + detached base label), before any child
     /// aggregation — the shared source for the three text getters.
-    fn own_text_labels(&self) -> Vec<TextLabel> {
+    pub(crate) fn own_text_labels(&self) -> Vec<TextLabel> {
         if !self.visible() {
             return Vec::new();
         }
@@ -814,10 +804,11 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
     }
 
     /// Own text with font + bounds: [`Paint::text_bounds`] when the widget provides it, else a
-    /// replica of the `Element` default's scroll-ancestor viewport clipping. The legacy tuple
-    /// getters serve `widget_font` for every label; the paint walk serves
-    /// [`Paint::text_font`] for the prim-derived labels (see `own_labels_for_walk`).
-    fn own_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+    /// replica of the deleted `Element` default's scroll-ancestor viewport clipping, with
+    /// `widget_font` on every label (the legacy tuple convention). pub(crate) so legacy
+    /// composites (TreeList, List) can read their concrete Adapted fields' labels now that
+    /// the trait getters are gone.
+    pub(crate) fn own_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
         self.own_labels_with_prim_font(ctx, Paint::widget_font(&self.inner))
     }
 
@@ -1069,19 +1060,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         }
     }
 
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut items = Vec::new();
-        if self.visible() {
-            // Own shaped buffers ([`Paint::text_items`] — StatusBar's manual-host path), then
-            // the container recursion.
-            items.extend(Paint::text_items(&self.inner));
-            for child in self.visible_children() {
-                items.extend(unsafe { &*child }.get_text_items());
-            }
-        }
-        items
-    }
-
     fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
         if self.visible() {
             let rect = self.content_rect();
@@ -1281,53 +1259,10 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         }
     }
 
-    /// Text derived from the [`Paint::paint`] `Text` prims (one source of truth for what the
-    /// widget draws — inline labels, readouts), plus the base-label text for detached-label
-    /// widgets (drawn by the adapter, since the label lives on the base).
-    /// The legacy bounded-text getters, honoring [`Paint::text_bounds`] (Graph clips node
-    /// names to its own rect) and aggregating container children — each child contributes its
-    /// own getter of the same kind, so its fonts/bounds are preserved (the Layer pattern).
-    /// Without a text-bounds hook, `text_labels_with_bounds` matches the unbounded `Element`
-    /// default, and `text_labels_with_font_and_bounds` replicates the default's
-    /// scroll-ancestor walk (an overriding impl can no longer call it).
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let bounds = Paint::text_bounds(&self.inner, self.content_rect());
-        let mut out: Vec<_> = self.own_text_labels().into_iter().map(|l| (l, bounds)).collect();
-        if self.visible() {
-            for child in self.visible_children() {
-                out.extend(unsafe { &*child }.text_labels_with_bounds(ctx));
-            }
-        }
-        out
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        // Per-label font+bounds escape hatch (ParametersBg): the widget's view is served
-        // verbatim, children included — no aggregation on top.
-        if Paint::serves_legacy_labels(&self.inner) {
-            if !self.visible() {
-                return Vec::new();
-            }
-            return Paint::legacy_labels_with_font_and_bounds(&self.inner, self.content_rect(), ctx);
-        }
-        let mut own = self.own_labels_with_font_and_bounds(ctx);
-        if self.visible() {
-            for child in self.visible_children() {
-                own.extend(unsafe { &*child }.text_labels_with_font_and_bounds(ctx));
-            }
-        }
-        own
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut out = self.own_text_labels();
-        if self.visible() {
-            for child in self.visible_children() {
-                out.extend(unsafe { &*child }.text_labels());
-            }
-        }
-        out
-    }
+    // The legacy per-widget text getters are deleted from `Element`: this adapter's text
+    // reaches the frame through `paint_self` above (prim-derived own labels + the
+    // detached base label), and composites that need a concrete Adapted child's labels
+    // call `own_labels_with_font_and_bounds` directly (pub(crate)).
 
     // --- Reverse bridges: [`Paint::paint`] output converted back to the legacy geometry
     // getters external render loops read (cce-test-interface's `all_*` calls, `render_widget`'s