git.lucas.co / cce-gallery
widget gallery and compositor test bench

commit0c3bf74e88712f539360231f71ffb762eb2acac0
parent88da973a67
authorLucas Galante <[email protected]>
date2026-07-11 23:37
refactor: gallery containers go app-local — last constructor of the container types (Phase 6as)

ControlPanel copies in verbatim (path-rewritten); Plate / SectionContainer /
Backplate become passive lookalikes replicating the legacy emission
surfaces: Plate's config-color-at-plate-opacity rule with blur negation,
label-offset background shift and model-drawn label; SectionContainer as
the childless header row the panel actually used (title + separator,
matched by base label); Backplate's opacity-forced base color, radius, and
bevel. Import swap only — constructions and the panel downcasts compile
unchanged. Gallery A/B: empty diff mask at 5% (raw AE is desktop-behind
translucency); no app constructs ControlPanel, SectionContainer, Plate, or
Backplate from cce-ui anymore.

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

 src/main.rs       |   6 +-
 src/ti_widgets.rs | 773 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 777 insertions(+), 2 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 66c2075..4a11d18 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,10 @@
 use cce_ui::widget::{
     Button, Checkbox, ContentBg, Dropdown, Label, Paginator, Panel, ProgressBar, RangeSlider, Slider, Spinbox, StatusBar,
-    Toggle, Element, Trackpad, hover_animation, TextBox, Plate, CornerRadii, Backplate, MenuBar, SectionContainer,
-    Ramp, RampKey, ColorRamp, ControlPanel, MouseButton, ElementState, Key, NamedKey, KeyEvent, MouseScrollDelta
+    Toggle, Element, Trackpad, hover_animation, TextBox, CornerRadii, MenuBar, 
+    Ramp, RampKey, ColorRamp, MouseButton, ElementState, Key, NamedKey, KeyEvent, MouseScrollDelta
 };
+mod ti_widgets;
+use ti_widgets::{Backplate, ControlPanel, Plate, SectionContainer};
 use cce_ui::engine::{Vertex, quad_vertices, LogicalSize, LogicalPosition};
 use wayland_client::QueueHandle;
 
diff --git a/src/ti_widgets.rs b/src/ti_widgets.rs
new file mode 100644
index 0000000..eff943c
--- /dev/null
+++ b/src/ti_widgets.rs
@@ -0,0 +1,773 @@
+//! Test-interface-owned copies of the dissolved cce-ui containers (Phase 6as): the
+//! gallery is the last constructor of ControlPanel / SectionContainer / Plate /
+//! Backplate, and it needs them only as demo chrome — the panel verbatim, the other
+//! three as passive lookalikes replicating the legacy types' exact emission surfaces
+//! (color rules, corner radius/rounding, borders, separators, labels, hit shapes).
+
+use cce_ui::colors;
+use cce_ui::widget::*;
+use cce_ui::widget::ScrollBox;
+
+pub struct ControlPanel {
+    pub base: Widget,
+    pub children: Vec<*mut (dyn Element + 'static)>,
+    pub parent: Option<*mut (dyn Element + 'static)>,
+    pub scroll_box: ScrollBox,
+    pub active_drag_widget: Option<*mut (dyn Element + 'static)>,
+}
+
+impl ControlPanel {
+    pub fn new() -> Self {
+        let mut sb = ScrollBox::new();
+        sb.show_border = false;
+        sb.show_background = false;
+        Self {
+            base: Widget::new(),
+            children: Vec::new(),
+            parent: None,
+            scroll_box: sb,
+            active_drag_widget: None,
+        }
+    }
+
+    pub fn add_child(&mut self, child: *mut (dyn Element + 'static)) {
+        self.children.push(child);
+        self.scroll_box.children.push(child);
+    }
+
+    pub fn with_label(mut self, label: &str) -> Self {
+        self.base.label = Some(label.to_string());
+        self
+    }
+}
+
+impl Element for ControlPanel {
+    cce_ui::impl_widget_base!(ControlPanel);
+
+    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;
+        }
+
+        // Check if cursor is inside child popovers first (scrolled coordinates)
+        if let Some(pop_rect) = self.popover_rect() {
+            if px >= pop_rect.0 && px <= pop_rect.0 + pop_rect.2 && py >= pop_rect.1 && py <= pop_rect.1 + pop_rect.3 {
+                return true;
+            }
+        }
+
+        // Otherwise check the panel itself
+        let (x, y, w, h) = self.rect();
+        if w <= 0.0 || h <= 0.0 {
+            return false;
+        }
+        px >= x && px <= x + w && py >= y && py <= y + h
+    }
+
+    fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
+        let mut changed = self.scroll_box.tick(dt, ctx);
+        unsafe {
+            for child_ptr in &self.children {
+                if (**child_ptr).tick(dt, ctx) {
+                    changed = true;
+                }
+            }
+        }
+        changed
+    }
+
+    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> { self.parent }
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
+        self.parent = parent;
+    }
+
+    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
+        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 cce_ui::scene::paint::PaintCtx) {
+        cce_ui::scene::painter::paint_legacy_leaf(self, ui, ctx, self.scrolled_child_labels(ui));
+    }
+
+    fn color(&self) -> [f32; 4] {
+        colors::control_panel_color()
+    }
+
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
+
+        self.scroll_box.set_rect(x, y, w, h);
+
+        let self_ptr = self.as_ptr_mut();
+        let mut dummy = cce_ui::context::UiContext::new();
+        let self_ptr_option = Some(self_ptr);
+
+        let padding = cce_ui::layout::control_panel_padding();
+        let gap = cce_ui::layout::control_panel_gap();
+        let mut col = ColumnLayout::new(x, y, w - 12.0, gap, padding); // 12px reserved for scrollbar track
+
+        unsafe {
+            let mut create_btn: Option<*mut dyn Element> = None;
+            let mut tile_btn: Option<*mut dyn Element> = None;
+            let mut opacity_toggle: Option<*mut dyn Element> = None;
+            let mut enable_toggle: Option<*mut dyn Element> = None;
+            let mut slider: Option<*mut dyn Element> = None;
+            let mut slider_label: Option<*mut dyn Element> = None;
+            let mut type_dd: Option<*mut dyn Element> = None;
+            let mut shape_dd: Option<*mut dyn Element> = None;
+            let mut border_style_dd: Option<*mut dyn Element> = None;
+            let mut width_spin: Option<*mut dyn Element> = None;
+            let mut height_spin: Option<*mut dyn Element> = None;
+            let mut backplate_toggle: Option<*mut dyn Element> = None;
+            let mut menubar_toggle: Option<*mut dyn Element> = None;
+            let mut statusbar_toggle: Option<*mut dyn Element> = None;
+            let mut border_sec: Option<*mut dyn Element> = None;
+            let mut bevel_toggle: Option<*mut dyn Element> = None;
+            let mut border_width_spin: Option<*mut dyn Element> = None;
+            let mut bevel_depth_spin: Option<*mut dyn Element> = None;
+            let mut win_sec: Option<*mut dyn Element> = None;
+            let mut bevel_shape_btn: Option<*mut dyn Element> = None;
+
+            for &child_ptr in &self.children {
+                let child = &mut *child_ptr;
+                child.set_parent(self_ptr_option, &mut dummy);
+                let label = child.base().and_then(|b| b.label.as_ref()).map(|s| s.as_str()).unwrap_or("");
+                match label {
+                    "Create Window" => create_btn = Some(child_ptr),
+                    "Tile Windows" => tile_btn = Some(child_ptr),
+                    "Opacity" => opacity_toggle = Some(child_ptr),
+                    "Enable" => enable_toggle = Some(child_ptr),
+                    "Transparency Level" => slider_label = Some(child_ptr),
+                    "Border" => border_sec = Some(child_ptr),
+                    "Window Elements" => win_sec = Some(child_ptr),
+                    "Window Type" => type_dd = Some(child_ptr),
+                    "Window Shape" => shape_dd = Some(child_ptr),
+                    "Border Style" => border_style_dd = Some(child_ptr),
+                    "Width" => width_spin = Some(child_ptr),
+                    "Height" => height_spin = Some(child_ptr),
+                    "Backplate" => backplate_toggle = Some(child_ptr),
+                    "MenuBar" => menubar_toggle = Some(child_ptr),
+                    "StatusBar" => statusbar_toggle = Some(child_ptr),
+                    "Bevel" => bevel_toggle = Some(child_ptr),
+                    "Border Width" => border_width_spin = Some(child_ptr),
+                    "Bevel Depth" => bevel_depth_spin = Some(child_ptr),
+                    "Bevel Shape..." => bevel_shape_btn = Some(child_ptr),
+                    _ => {
+                        if child.base().is_some() && child.base().unwrap().label.is_none() {
+                            slider = Some(child_ptr);
+                        }
+                    }
+                }
+            }
+
+            if let (Some(c), Some(t)) = (create_btn, tile_btn) {
+                col.add_row(&[c, t], 28.0, 12.0);
+            } else {
+                if let Some(c) = create_btn {
+                    col.add_widget(&mut *c, 28.0);
+                }
+                if let Some(t) = tile_btn {
+                    col.add_widget(&mut *t, 28.0);
+                }
+            }
+
+            if let (Some(w_sp), Some(h_sp)) = (width_spin, height_spin) {
+                col.add_row(&[w_sp, h_sp], 42.0, 12.0);
+            } else {
+                if let Some(w_sp) = width_spin {
+                    col.add_widget(&mut *w_sp, 42.0);
+                }
+                if let Some(h_sp) = height_spin {
+                    col.add_widget(&mut *h_sp, 42.0);
+                }
+            }
+
+            if let Some(t_dd) = type_dd {
+                col.add_widget(&mut *t_dd, 44.0);
+            }
+            if let Some(s_dd) = shape_dd {
+                col.add_widget(&mut *s_dd, 44.0);
+            }
+            if let Some(op_t) = opacity_toggle {
+                col.add_widget(&mut *op_t, 28.0);
+            }
+            if let Some(en_t) = enable_toggle {
+                col.add_widget(&mut *en_t, 28.0);
+            }
+            if let Some(sl_lbl) = slider_label {
+                col.add_widget(&mut *sl_lbl, 12.0);
+            }
+            if let Some(sl) = slider {
+                col.add_widget(&mut *sl, 20.0);
+            }
+
+            if let Some(w_s) = win_sec {
+                col.add_widget(&mut *w_s, 20.0);
+            }
+            let toggles = [backplate_toggle, menubar_toggle, statusbar_toggle];
+            let active_toggles: Vec<*mut dyn Element> = toggles.iter().filter_map(|&t| t).collect();
+            if !active_toggles.is_empty() {
+                col.add_row(&active_toggles, 28.0, 10.0);
+            }
+
+            if let Some(b_s) = border_sec {
+                col.add_widget(&mut *b_s, 20.0);
+            }
+            if let Some(bs_dd) = border_style_dd {
+                col.add_widget(&mut *bs_dd, 44.0);
+            }
+            if let Some(bev_t) = bevel_toggle {
+                col.add_widget(&mut *bev_t, 28.0);
+            }
+
+            if let (Some(bw_sp), Some(bd_sp)) = (border_width_spin, bevel_depth_spin) {
+                col.add_row(&[bw_sp, bd_sp], 42.0, 12.0);
+            } else {
+                if let Some(bw_sp) = border_width_spin {
+                    col.add_widget(&mut *bw_sp, 42.0);
+                }
+                if let Some(bd_sp) = bevel_depth_spin {
+                    col.add_widget(&mut *bd_sp, 42.0);
+                }
+            }
+            if let Some(bs_btn) = bevel_shape_btn {
+                col.add_widget(&mut *bs_btn, 28.0);
+            }
+
+            let total_h = col.current_y();
+            self.scroll_box.update_bounds(total_h, y, h);
+        }
+    }
+
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        (true, true, true, true)
+    }
+
+    fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+        let mut quads = Vec::new();
+        let (x, y, w, h) = self.rect();
+
+        // 1. Background
+        quads.push((x, y, w, h, 0.0, self.color(), (false, false, false, false)));
+
+        // 2. Borders
+        let border_color = colors::control_panel_border_color();
+        quads.push((x, y, w, 1.0, 0.0, border_color, (false, false, false, false)));
+        quads.push((x, y + h - 1.0, w, 1.0, 0.0, border_color, (false, false, false, false)));
+        quads.push((x, y, 1.0, h, 0.0, border_color, (false, false, false, false)));
+        quads.push((x + w - 1.0, y, 1.0, h, 0.0, border_color, (false, false, false, false)));
+
+        // 3. Child elements clipped to viewport bounds
+        let scroll_y = self.scroll_box.scroll_y;
+        let y_start = y;
+        let y_end = y + h;
+
+        unsafe {
+            for child_ptr in &self.children {
+                let child = &**child_ptr;
+                let solid_border_opt = if child.type_name() == "Toggle" { None } else { child.solid_border() };
+                let (cx, cy, cw, ch) = child.rect();
+
+                if let Some((b_color, _thickness)) = solid_border_opt {
+                    let cy_shifted = cy - scroll_y;
+                    let cy_top = cy_shifted;
+                    let cy_bottom = cy_shifted + ch;
+                    if cy_bottom > y_start && cy_top < y_end {
+                        let visible_top = cy_top.max(y_start);
+                        let visible_bottom = cy_bottom.min(y_end);
+                        let visible_h = visible_bottom - visible_top;
+                        if visible_h > 0.0 {
+                            let radii_adjusted = if visible_top > cy_top || visible_bottom < cy_bottom {
+                                0.0
+                            } else {
+                                child.corner_radius()
+                            };
+                            quads.push((
+                                cx,
+                                visible_top,
+                                cw,
+                                visible_h,
+                                radii_adjusted,
+                                b_color,
+                                child.rounded_corners(),
+                            ));
+                        }
+                    }
+                }
+
+                for (qx, qy, qw, qh, qr, qc, qcorners) in child.all_rounded_quads(ctx) {
+                    let mut rx = qx;
+                    let mut ry = qy;
+                    let mut rw = qw;
+                    let mut rh = qh;
+                    let mut rqr = qr;
+
+                    if let Some((_, thickness)) = solid_border_opt {
+                        if (qx - cx).abs() < 0.1 && (qy - cy).abs() < 0.1 && (qw - cw).abs() < 0.1 && (qh - ch).abs() < 0.1 {
+                            rx += thickness;
+                            ry += thickness;
+                            rw -= 2.0 * thickness;
+                            rh -= 2.0 * thickness;
+                            rqr = (qr - thickness).max(0.0);
+                        }
+                    }
+
+                    let qy_shifted = ry - scroll_y;
+                    let qy_top = qy_shifted;
+                    let qy_bottom = qy_shifted + rh;
+                    if qy_bottom > y_start && qy_top < y_end {
+                        let visible_top = qy_top.max(y_start);
+                        let visible_bottom = qy_bottom.min(y_end);
+                        let visible_h = visible_bottom - visible_top;
+                        if visible_h > 0.0 {
+                            let radii_adjusted = if visible_top > qy_top || visible_bottom < qy_bottom {
+                                0.0
+                            } else {
+                                rqr
+                            };
+                            quads.push((rx, visible_top, rw, visible_h, radii_adjusted, qc, qcorners));
+                        }
+                    }
+                }
+            }
+        }
+
+        // 4. Scrollbar
+        for (sx, sy, sw, sh, sc) in self.scroll_box.extra_quads() {
+            quads.push((sx, sy, sw, sh, 0.0, sc, (false, false, false, false)));
+        }
+
+        quads
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        let mut quads = Vec::new();
+        let (_x, y, _w, h) = self.rect();
+        let scroll_y = self.scroll_box.scroll_y;
+        let y_start = y;
+        let y_end = y + h;
+        let ctx_dummy = cce_ui::context::UiContext::new();
+
+        unsafe {
+            for child_ptr in &self.children {
+                let child = &**child_ptr;
+                let (cx, cy, cw, ch) = child.rect();
+                let has_rounded = child.rounded_corners() != (false, false, false, false);
+                let has_bg = child.color()[3].abs() > 0.001;
+
+                for (qx, qy, qw, qh, qc) in child.all_quads(&ctx_dummy) {
+                    if has_rounded && has_bg && (qx - cx).abs() < 0.1 && (qy - cy).abs() < 0.1 && (qw - cw).abs() < 0.1 && (qh - ch).abs() < 0.1 {
+                        continue;
+                    }
+                    let qy_shifted = qy - scroll_y;
+                    let qy_top = qy_shifted;
+                    let qy_bottom = qy_shifted + qh;
+                    if qy_bottom > y_start && qy_top < y_end {
+                        let visible_top = qy_top.max(y_start);
+                        let visible_bottom = qy_bottom.min(y_end);
+                        let visible_h = visible_bottom - visible_top;
+                        if visible_h > 0.0 {
+                            quads.push((qx, visible_top, qw, visible_h, qc));
+                        }
+                    }
+                }
+            }
+        }
+
+        quads.extend(self.scroll_box.extra_quads());
+        quads
+    }
+
+    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        let mut arcs = Vec::new();
+        let scroll_y = self.scroll_box.scroll_y;
+        let y_start = self.base.y;
+        let y_end = self.base.y + self.base.h;
+
+        unsafe {
+            for child_ptr in &self.children {
+                let child = &**child_ptr;
+                for (cx, cy, r, t, start, end, color) in child.extra_arcs() {
+                    let cy_shifted = cy - scroll_y;
+                    if cy_shifted + r > y_start && cy_shifted - r < y_end {
+                        arcs.push((cx, cy_shifted, r, t, start, end, color));
+                    }
+                }
+            }
+        }
+        arcs
+    }
+
+    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
+        let scroll_y = self.scroll_box.scroll_y;
+        unsafe {
+            for child_ptr in &self.children {
+                let child = &mut **child_ptr;
+                let old_y = child.base().map(|b| b.y).unwrap_or(0.0);
+                if let Some(b) = child.base_mut() {
+                    b.y = old_y - scroll_y;
+                }
+                let res = child.popover_rect();
+                if let Some(b) = child.base_mut() {
+                    b.y = old_y;
+                }
+                if res.is_some() {
+                    return res;
+                }
+            }
+        }
+        None
+    }
+
+    fn render_popover(&self, pc: &mut dyn cce_ui::layout::RenderTarget) {
+        let scroll_y = self.scroll_box.scroll_y;
+        unsafe {
+            for child_ptr in &self.children {
+                if (**child_ptr).popover_rect().is_some() {
+                    let child = &mut **child_ptr;
+                    let old_y = child.base().map(|b| b.y).unwrap_or(0.0);
+                    if let Some(b) = child.base_mut() {
+                        b.y = old_y - scroll_y;
+                    }
+                    child.render_popover(pc);
+                    if let Some(b) = child.base_mut() {
+                        b.y = old_y;
+                    }
+                }
+            }
+        }
+    }
+
+    fn draggable(&self) -> bool {
+        self.scroll_box.draggable() || self.active_drag_widget.is_some()
+    }
+
+    fn drag_begin(&mut self, px: f32, py: f32) {
+        if self.scroll_box.hit_test_scrollbar(px, py) {
+            self.scroll_box.drag_begin(px, py);
+            return;
+        }
+
+        let scroll_y = self.scroll_box.scroll_y;
+        let py_translated = py + scroll_y;
+        if let Some(child_ptr) = self.active_drag_widget {
+            unsafe {
+                (*child_ptr).drag_begin(px, py_translated);
+            }
+        }
+    }
+
+    fn drag_update(&mut self, px: f32, py: f32) -> bool {
+        if self.scroll_box.draggable() {
+            return self.scroll_box.drag_update(px, py);
+        }
+
+        let scroll_y = self.scroll_box.scroll_y;
+        let py_translated = py + scroll_y;
+        if let Some(child_ptr) = self.active_drag_widget {
+            unsafe {
+                return (*child_ptr).drag_update(px, py_translated);
+            }
+        }
+        false
+    }
+
+    fn drag_end(&mut self) {
+        self.scroll_box.drag_end();
+        if let Some(child_ptr) = self.active_drag_widget {
+            unsafe {
+                (*child_ptr).drag_end();
+            }
+            self.active_drag_widget = None;
+        }
+    }
+
+    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+        if state == ElementState::Pressed {
+            self.active_drag_widget = None;
+        }
+
+        if self.scroll_box.mouse_input(button, state, px, py, ctx) {
+            return true;
+        }
+
+        let scroll_y = self.scroll_box.scroll_y;
+        let py_translated = py + scroll_y;
+        let (_x, y, _w, h) = self.rect();
+
+        unsafe {
+            for child_ptr in &self.children {
+                let has_popover = (**child_ptr).popover_rect().is_some();
+                // Only dispatch if the click Y is inside the viewport or the child has an active popover
+                if has_popover || (py >= y && py <= y + h) {
+                    if (**child_ptr).mouse_input(button, state, px, py_translated, ctx) {
+                        if state == ElementState::Pressed && (**child_ptr).draggable() {
+                            self.active_drag_widget = Some(*child_ptr);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+        false
+    }
+
+    fn cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+        let mut changed = self.scroll_box.cursor_moved(px, py, ctx);
+
+        let scroll_y = self.scroll_box.scroll_y;
+        let py_translated = py + scroll_y;
+        unsafe {
+            for child_ptr in &self.children {
+                if (**child_ptr).cursor_moved(px, py_translated, ctx) {
+                    changed = true;
+                }
+            }
+        }
+        changed
+    }
+
+    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
+        if self.scroll_box.mouse_wheel(delta, px, py, ctx) {
+            return true;
+        }
+
+        let scroll_y = self.scroll_box.scroll_y;
+        let py_translated = py + scroll_y;
+        unsafe {
+            for child_ptr in &self.children {
+                if (**child_ptr).mouse_wheel(delta, px, py_translated, ctx) {
+                    return true;
+                }
+            }
+        }
+        false
+    }
+
+}
+
+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();
+        let mut labels = Vec::new();
+        for &child_ptr in &self.children {
+            let mut scratch = cce_ui::scene::paint::PaintCtx::new();
+            cce_ui::scene::painter::append_widget_text(ctx, unsafe { &*child_ptr }, &mut scratch);
+            for item in scratch.finish().items {
+                if let cce_ui::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);
+                        let nb = (b - scroll_y).min(y + h);
+                        Some([nl, nt, nr, nb])
+                    } else {
+                        Some([x, y, x + w, y + h])
+                    };
+                    labels.push((
+                        TextLabel { text, x: lx, y: ly - scroll_y, font_size, color },
+                        font,
+                        new_bounds,
+                    ));
+                }
+            }
+        }
+        labels
+    }
+}
+
+/// Gallery exhibit lookalike of the dissolved cce-ui `Plate`: default plate color rule
+/// (config plate color, else page color) at plate opacity, alpha negated under blur,
+/// plate corner radius/rounding, config border, detached-top label handled like the
+/// legacy plate (background shifted below the label, label drawn in paint_self).
+pub struct Plate {
+    base: Widget,
+    blur: bool,
+}
+
+impl Plate {
+    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+        let mut base = Widget::new();
+        base.x = x;
+        base.y = y;
+        base.w = w;
+        base.h = h;
+        Self { base, blur: false }
+    }
+
+    pub fn with_blur(mut self, blur: bool) -> Self {
+        self.blur = blur;
+        self
+    }
+
+    pub fn with_label(mut self, label: &str) -> Self {
+        self.base.label = Some(label.to_string());
+        self
+    }
+}
+
+impl Element for Plate {
+    cce_ui::impl_widget_base!(Plate);
+
+    fn color(&self) -> [f32; 4] {
+        let mut c = if let Some(c) = colors::plate_color() {
+            c
+        } else {
+            colors::page_low_color()
+        };
+        c[3] *= cce_ui::layout::plate_opacity();
+        if self.blur && colors::plate_blur() {
+            c[3] = -c[3].abs();
+        }
+        c
+    }
+
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        let r = cce_ui::layout::plate_corner_radius();
+        if r > 0.0 { (true, true, true, true) } else { (false, false, false, false) }
+    }
+
+    fn corner_radius(&self) -> f32 {
+        cce_ui::layout::plate_corner_radius()
+    }
+
+    fn solid_border(&self) -> Option<([f32; 4], f32)> {
+        colors::plate_border_color().map(|bc| (bc, colors::plate_border_thickness()))
+    }
+
+    fn all_rounded_quads(&self, _ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
+        let (r1, r2, r3, r4) = self.rounded_corners();
+        if !(r1 || r2 || r3 || r4) {
+            return Vec::new();
+        }
+        let (x, y, w, h) = self.rect();
+        let label_off = self.base.label_offset();
+        let c = self.color();
+        if c[3].abs() > 0.001 {
+            vec![(x, y + label_off, w, h - label_off, self.corner_radius(), c, (r1, r2, r3, r4))]
+        } else {
+            Vec::new()
+        }
+    }
+
+    fn paint_self(&self, _ui: &UiContext, ctx: &mut cce_ui::scene::paint::PaintCtx) {
+        if let Some(ref label) = self.base.label {
+            let (_, font_size) = cce_ui::layout::control_label_font_parsed();
+            ctx.text(label.clone(), self.base.x, self.base.y, font_size, colors::control_label_color_u8());
+        }
+    }
+}
+
+/// Gallery lookalike of the dissolved cce-ui `SectionContainer` as the ControlPanel uses
+/// it: a childless section header row — the title label plus the SectionHeader separator
+/// line. The panel matches sections by the base label.
+pub struct SectionContainer {
+    base: Widget,
+}
+
+impl SectionContainer {
+    pub fn new(title: &str) -> Self {
+        let mut base = Widget::new();
+        base.label = Some(title.to_string());
+        Self { base }
+    }
+}
+
+impl Element for SectionContainer {
+    cce_ui::impl_widget_base!(SectionContainer);
+
+    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 paint_self(&self, _ui: &UiContext, ctx: &mut cce_ui::scene::paint::PaintCtx) {
+        for (qx, qy, qw, qh, qc) in self.extra_quads() {
+            ctx.quad(cce_ui::scene::layout::Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+        }
+        ctx.text(
+            self.base.label.clone().unwrap_or_default(),
+            self.base.x + 12.0,
+            self.base.y,
+            14.0,
+            [212, 212, 212],
+        );
+    }
+}
+
+/// Child-window background lookalike of the dissolved cce-ui `Backplate`: the base color
+/// forced to the configured backplate opacity, backplate corner radius, immovable.
+pub struct Backplate {
+    base: Widget,
+    bevel: Option<f32>,
+}
+
+impl Backplate {
+    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+        let mut base = Widget::new();
+        base.x = x;
+        base.y = y;
+        base.w = w;
+        base.h = h;
+        Self { base, bevel: None }
+    }
+
+    pub fn with_movable(self, _movable: bool) -> Self {
+        self
+    }
+
+    pub fn with_bevel(mut self, bevel: bool, thickness: f32) -> Self {
+        self.bevel = if bevel { Some(thickness) } else { None };
+        self
+    }
+}
+
+impl Element for Backplate {
+    cce_ui::impl_widget_base!(Backplate);
+
+    fn color(&self) -> [f32; 4] {
+        let mut c = cce_ui::color::page_low_color();
+        if c[3] > 0.001 {
+            c[3] = cce_ui::color::active_backplate_opacity();
+        }
+        c
+    }
+
+    fn corner_radius(&self) -> f32 {
+        cce_ui::color::backplate_corner_radius()
+    }
+
+    fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+        if self.corner_radius() > 0.1 { (true, true, true, true) } else { (false, false, false, false) }
+    }
+
+    fn plate_bevel(&self) -> Option<f32> {
+        self.bevel
+    }
+}