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

commit76f4af343b4ec08587b11e92db5a3c61e0bf4f17
parent628e4ae9e4
authorLucas Galante <[email protected]>
date2026-07-12 00:57
refactor(widget)!: DELETE Switcher — zero constructors (Phase 6aw)

First payoff of the controller-capability decision (option 2: defer to
concrete typing, no registry): Switcher was the single biggest holder
of as_*_controller capability-dispatch sites (17 — it implemented
MenuController by forwarding to its active pane through
as_menu_controller_mut on dyn Element), and it has had zero
constructors workspace-wide since cce-system-settings dissolved its
switcher in Phase 6u. Deleted outright with its exports.

168 tests pass (its in-file test died with it); full workspace builds —
no consumer existed, so no runtime surface changes.

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

 src/widget/container/mod.rs      |   2 -
 src/widget/container/switcher.rs | 343 ---------------------------------------
 src/widget/mod.rs                |   2 +-
 3 files changed, 1 insertion(+), 346 deletions(-)

diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index 46d80a4..acba67b 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -7,7 +7,6 @@ pub mod menu;
 pub mod breadcrumb;
 pub mod spreadsheet;
 pub mod scroll_box;
-pub mod switcher;
 pub mod paginator;
 pub mod treelist;
 pub mod vbox;
@@ -22,7 +21,6 @@ pub use menu::MenuBar;
 pub use breadcrumb::Breadcrumb;
 pub use spreadsheet::Spreadsheet;
 pub use scroll_box::ScrollBox;
-pub use switcher::Switcher;
 pub use paginator::Paginator;
 pub use treelist::{TreeList, TreeElement};
 pub use vbox::VBox;
diff --git a/src/widget/container/switcher.rs b/src/widget/container/switcher.rs
deleted file mode 100644
index b1e44b0..0000000
--- a/src/widget/container/switcher.rs
+++ /dev/null
@@ -1,343 +0,0 @@
-//! Narrow-trait `Switcher` (Phase 5n) — the first container across: it owns externally-managed
-//! child pointers (pages) and exposes exactly one of them at a time. The adapter's container
-//! concern does the subtree plumbing (geometry/text aggregation, tick/popover/text-item
-//! recursion, hit-through-children), filtered to the active child by
-//! [`Layout::child_visible`]; the model keeps the legacy specifics: parent-clamped rects,
-//! active-child arrangement, event proxying (via `EventCtx::ui`), paint-property proxies, and
-//! the [`MenuController`] delegation to the active child.
-
-use crate::scene::layout::Rect;
-use crate::scene::paint::PaintCtx;
-use crate::widget::{
-    Adapted, Element, ElementState, Event, EventCtx, Input, Layout, MenuController, Paint,
-    UiContext,
-};
-
-#[derive(Debug, Clone)]
-pub struct Switcher {
-    children: Vec<*mut (dyn Element + 'static)>,
-    parent: Option<*mut (dyn Element + 'static)>,
-    active_index: Option<usize>,
-}
-
-impl Switcher {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Adapted<Switcher> {
-        let mut s = Adapted::new(Switcher { children: Vec::new(), parent: None, active_index: None });
-        Element::set_rect(&mut s, x, y, w, h);
-        s
-    }
-
-    pub fn set_active_index(&mut self, index: Option<usize>) {
-        self.active_index = index;
-        for (i, &child_ptr) in self.children.iter().enumerate() {
-            unsafe {
-                (*child_ptr).set_visible(self.active_index == Some(i));
-            }
-        }
-    }
-
-    pub fn active_index(&self) -> Option<usize> {
-        self.active_index
-    }
-
-    fn active_child(&self) -> Option<*mut (dyn Element + 'static)> {
-        self.children.get(self.active_index?).copied()
-    }
-}
-
-impl Layout for Switcher {
-    fn has_container_children(&self) -> bool {
-        true
-    }
-
-    fn container_children(&self) -> Vec<*mut (dyn Element + 'static)> {
-        self.children.clone()
-    }
-
-    fn child_added(&mut self, child: *mut (dyn Element + 'static)) {
-        self.children.push(child);
-        // Sync visibility of the newly added child with the active selection.
-        let idx = self.children.len() - 1;
-        unsafe {
-            (*child).set_visible(self.active_index == Some(idx));
-        }
-    }
-
-    fn children_cleared(&mut self) {
-        self.children.clear();
-    }
-
-    fn parent_changed(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
-        self.parent = parent;
-    }
-
-    fn child_visible(&self, child: *mut (dyn Element + 'static)) -> bool {
-        self.active_child().map_or(false, |active| std::ptr::eq(active, child))
-    }
-
-    /// Legacy `set_rect` clamped the switcher into its parent's rect.
-    fn adjust_rect(&self, requested: Rect) -> Rect {
-        let Some(parent_ptr) = self.parent else {
-            return requested;
-        };
-        let (px, py, pw, ph) = unsafe { (*parent_ptr).rect() };
-        let cx = requested.x.clamp(px, px + pw.max(0.0));
-        let cy = requested.y.clamp(py, py + ph.max(0.0));
-        let cw = requested.width.min((px + pw.max(0.0) - cx).max(0.0));
-        let ch = requested.height.min((py + ph.max(0.0) - cy).max(0.0));
-        Rect { x: cx, y: cy, width: cw, height: ch }
-    }
-
-    /// The active child fills the switcher's rect.
-    fn arrange_children(&mut self, rect: Rect, _host: *mut (dyn Element + 'static)) {
-        if let Some(child) = self.active_child() {
-            unsafe {
-                (*child).set_rect(rect.x, rect.y, rect.width, rect.height);
-            }
-        }
-    }
-
-    fn layout_children_ctx(&mut self, rect: Rect, ctx: &mut UiContext) {
-        if let Some(child) = self.active_child() {
-            unsafe {
-                (*child).layout(
-                    crate::widget::Point { x: rect.x, y: rect.y },
-                    crate::widget::LayoutConstraints::new(rect.width, rect.width, rect.height, rect.height),
-                    ctx,
-                );
-            }
-        }
-    }
-}
-
-impl Paint for Switcher {
-    /// The switcher shows as whatever its active child shows as (legacy proxied `color`,
-    /// `rounded_corners`, and `solid_border` — style-property painters read these).
-    fn color(&self) -> [f32; 4] {
-        match self.active_child() {
-            Some(child) => unsafe { (*child).color() },
-            None => [0.0, 0.0, 0.0, 0.0],
-        }
-    }
-
-    fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
-        // Legacy kept the Element-default 12.0 radius and proxied the corner flags.
-        let corners = match self.active_child() {
-            Some(child) => unsafe { (*child).rounded_corners() },
-            None => (false, false, false, false),
-        };
-        Some((12.0, corners))
-    }
-
-    fn solid_border(&self) -> Option<([f32; 4], f32)> {
-        self.active_child().and_then(|child| unsafe { (*child).solid_border() })
-    }
-
-    /// No own geometry: the background is the active child's own business, and the adapter's
-    /// container aggregation carries the subtree on the legacy getters (the scene walk
-    /// recurses `children()` itself).
-    fn paint(&self, _rect: Rect, _ctx: &mut PaintCtx) {}
-}
-
-impl Input for Switcher {
-    fn blocks_backplate_drag(&self) -> bool {
-        false
-    }
-
-    fn hits_through_children(&self) -> bool {
-        true
-    }
-
-    /// Legacy `mouse_input` saw every press to unfocus the child on an outside click.
-    fn gates_presses(&self) -> bool {
-        false
-    }
-
-    fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
-        let Some(child_ptr) = self.active_child() else {
-            return false;
-        };
-        let widget = unsafe { &mut *child_ptr };
-        match event {
-            Event::MouseButton { button, state, x: px, y: py, .. } => {
-                let Some(ui) = ectx.ui.as_deref_mut() else {
-                    return false;
-                };
-                // Faithful port of the legacy body, including the double dispatch while a
-                // popover is open.
-                if widget.popover_rect().is_some() {
-                    if widget.mouse_input(*button, *state, *px, *py, ui) {
-                        return true;
-                    }
-                }
-                if widget.mouse_input(*button, *state, *px, *py, ui) {
-                    return true;
-                }
-                if *state == ElementState::Pressed && !widget.hit_test(*px, *py, ui) {
-                    widget.unfocus();
-                }
-                false
-            }
-            Event::PointerMove { x: px, y: py, .. } => {
-                if widget.is_dragging() {
-                    return widget.drag_update(*px, *py);
-                }
-                let Some(ui) = ectx.ui.as_deref_mut() else {
-                    return false;
-                };
-                widget.cursor_moved(*px, *py, ui)
-            }
-            Event::MouseWheel { delta, x: px, y: py, .. } => {
-                let Some(ui) = ectx.ui.as_deref_mut() else {
-                    return false;
-                };
-                widget.mouse_wheel(delta, *px, *py, ui)
-            }
-            Event::KeyInput(key_event) => {
-                let Some(ui) = ectx.ui.as_deref_mut() else {
-                    return false;
-                };
-                widget.keyboard_input(key_event, ui)
-            }
-            _ => false,
-        }
-    }
-
-    fn menu_controller(&self) -> Option<&dyn MenuController> {
-        Some(self)
-    }
-    fn menu_controller_mut(&mut self) -> Option<&mut dyn MenuController> {
-        Some(self)
-    }
-}
-
-impl MenuController for Switcher {
-    fn menu_click(&mut self) -> Option<(usize, usize)> {
-        unsafe { &mut *self.active_child()? }.as_menu_controller_mut()?.menu_click()
-    }
-    fn trigger_menu_click(&mut self, menu_idx: usize, item_idx: usize) {
-        if let Some(child) = self.active_child() {
-            if let Some(mc) = unsafe { &mut *child }.as_menu_controller_mut() {
-                mc.trigger_menu_click(menu_idx, item_idx);
-            }
-        }
-    }
-    fn set_item_checked(&mut self, menu_idx: usize, item_idx: usize, checked: bool) {
-        if let Some(child) = self.active_child() {
-            if let Some(mc) = unsafe { &mut *child }.as_menu_controller_mut() {
-                mc.set_item_checked(menu_idx, item_idx, checked);
-            }
-        }
-    }
-    fn set_menu_items(&mut self, menu_idx: usize, items: &[String]) {
-        if let Some(child) = self.active_child() {
-            if let Some(mc) = unsafe { &mut *child }.as_menu_controller_mut() {
-                mc.set_menu_items(menu_idx, items);
-            }
-        }
-    }
-    fn is_menu_bar(&self) -> bool {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.is_menu_bar()))
-            .unwrap_or(false)
-    }
-    fn is_menu_open(&self) -> bool {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.is_menu_open()))
-            .unwrap_or(false)
-    }
-    fn menu_items(&self) -> Vec<String> {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.menu_items()))
-            .unwrap_or_default()
-    }
-    fn menu_item_checked(&self) -> Vec<Option<bool>> {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.menu_item_checked()))
-            .unwrap_or_default()
-    }
-    fn is_vertical(&self) -> bool {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.is_vertical()))
-            .unwrap_or(false)
-    }
-    fn menu_names(&self) -> Vec<String> {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.menu_names()))
-            .unwrap_or_default()
-    }
-    fn menu_items_list(&self) -> Vec<Vec<String>> {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.menu_items_list()))
-            .unwrap_or_default()
-    }
-    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
-        self.active_child()
-            .and_then(|c| unsafe { &*c }.as_menu_controller().map(|mc| mc.menu_checked_list()))
-            .unwrap_or_default()
-    }
-    fn take_context_change(&mut self) -> Option<usize> {
-        unsafe { &mut *self.active_child()? }.as_menu_controller_mut()?.take_context_change()
-    }
-    fn set_context_selected(&mut self, selected: usize) {
-        if let Some(child) = self.active_child() {
-            if let Some(mc) = unsafe { &mut *child }.as_menu_controller_mut() {
-                mc.set_context_selected(selected);
-            }
-        }
-    }
-    fn set_center_items(&mut self, center: bool) {
-        if let Some(child) = self.active_child() {
-            if let Some(mc) = unsafe { &mut *child }.as_menu_controller_mut() {
-                mc.set_center_items(center);
-            }
-        }
-    }
-    fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> {
-        unsafe { &*self.active_child()? }.as_menu_controller()?.get_menu_items_at(px, py)
-    }
-}
-
-unsafe impl Send for Switcher {}
-unsafe impl Sync for Switcher {}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::context::UiContext;
-    use crate::widget::{Checkbox, Label};
-
-    #[test]
-    fn switcher_exposes_only_the_active_child() {
-        let mut ctx = UiContext::new();
-        let mut a = Label::new("page a");
-        let mut b = Checkbox::new();
-        let mut sw = Switcher::new(0.0, 0.0, 200.0, 100.0);
-        let (sw_id, sw_ptr) = (sw.id(), sw.as_ptr_mut());
-        ctx.register_widget(sw_id, sw_ptr);
-        Element::add_child(&mut sw, a.as_ptr_mut(), &mut ctx);
-        Element::add_child(&mut sw, b.as_ptr_mut(), &mut ctx);
-
-        // add_child parented both children back to the switcher and left them hidden (no
-        // active selection yet).
-        assert!(!Element::visible(&a) && !Element::visible(&b));
-        assert!(Element::children(&sw, &ctx).len() == 2);
-
-        // Activating a child shows it, arranges it into the switcher's rect, and routes the
-        // subtree getters through it alone.
-        sw.set_active_index(Some(1));
-        assert!(!Element::visible(&a) && Element::visible(&b));
-        Element::set_rect(&mut sw, 10.0, 20.0, 300.0, 150.0);
-        assert_eq!(Element::rect(&b), (10.0, 20.0, 300.0, 150.0), "active child fills the rect");
-        assert_ne!(Element::rect(&a), (10.0, 20.0, 300.0, 150.0), "inactive child untouched");
-
-        // Aggregation and hit-testing go through the active child only.
-        assert!(Element::is_child_visible(&sw, b.id()));
-        assert!(!Element::is_child_visible(&sw, a.id()));
-        assert!(Element::hit_test(&sw, 15.0, 25.0, &ctx), "hit lands on the active child");
-
-        // The menu-controller delegation returns None-ish defaults for non-menu children.
-        let elem: &dyn Element = &sw;
-        assert!(elem.as_menu_controller().unwrap().menu_items().is_empty());
-    }
-}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 6a81f9c..943ed1e 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -739,7 +739,7 @@ pub use self::container::{
     ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
     Header, ContentBg, ParametersBg,
     ScrollBox, MenuBar, Spreadsheet, Breadcrumb,
-    Switcher, Paginator, TreeList, TreeElement
+    Paginator, TreeList, TreeElement
 };
 pub use self::display::{
     TextLabel, Label, StyledLabel, LabelPrim, TextItem, Svg, UsageBar,