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