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

commitd53d0340dd0be3b10b2e0bbd2aa37ba8b9531b90
parent370be91645
authorLucas Galante <[email protected]>
date2026-07-12 14:08
refactor: the gallery roster is concretely typed — Roster replaces Vec<Box<dyn Element>> (Phase 6bb part 2)

The designer recipe applied to the two-mode gallery: 53 named concrete
slots (GallerySlots) for the gallery, and typed enums (ChildBg/ChildMain/
ChildAux3/ChildAux4 in ChildSlots) for the child window's runtime-variant
slots — a Roster enum carries whichever mode runs, boxed whole for pointer
stability. The historical numeric indexes keep addressing slots through
get_dyn/get_dyn_mut (positions vec, dispatch loops, page layouts, the
ControlPanel child list); demo_positions takes &GallerySlots. With this,
no app stores widgets behind Box<dyn Element> anywhere in the workspace —
the roster phase of the machinery retype is complete.

Verified: gallery static and after an identical panel wheel + click
sequence cross-build with empty 8% masks, both instances alive; child
(--type Ramp --backplate) A/B AE=553 (noise); non-Ramp child arms hit the
recorded pre-existing unconditional Ramp-downcast panic identically on
both builds.

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

 src/main.rs | 697 ++++++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 509 insertions(+), 188 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 4956ceb..eb02860 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@ use cce_ui::widget::{
 };
 mod ti_widgets;
 use ti_widgets::{Backplate, ControlPanel, Plate, SectionContainer};
+use cce_ui::widget::Adapted;
 use cce_ui::engine::{Vertex, quad_vertices, LogicalSize, LogicalPosition};
 use wayland_client::QueueHandle;
 
@@ -15,8 +16,318 @@ enum Page {
     Xdg,
 }
 
+
+/// The gallery roster, concretely typed (Phase 6bb): 53 named slots replacing the erased
+/// `Vec<Box<dyn Element>>`. The historical numeric indexes (positions vec, dispatch loops,
+/// page layouts) keep addressing the same slots through `get_dyn`/`get_dyn_mut`.
+pub struct GallerySlots {
+    pub menu_bar: Adapted<MenuBar>,
+    pub paginator: Adapted<Paginator>,
+    pub status_bar: Adapted<StatusBar>,
+    pub button_demo: Adapted<Button>,
+    pub label4: Adapted<Label>,
+    pub label5: Adapted<Label>,
+    pub label6: Adapted<Label>,
+    pub label7: Adapted<Label>,
+    pub label8: Adapted<Label>,
+    pub checkbox_demo: Adapted<Checkbox>,
+    pub toggle_demo: Adapted<Toggle>,
+    pub progress_demo: Adapted<ProgressBar>,
+    pub slider_demo: Adapted<Slider>,
+    pub spinbox_demo: Adapted<Spinbox>,
+    pub panel_demo: Adapted<Panel>,
+    pub create_window_btn: Adapted<Button>,
+    pub tile_windows_btn: Adapted<Button>,
+    pub opacity_toggle: Adapted<Toggle>,
+    pub label18: Adapted<Label>,
+    pub transparency_slider: Adapted<Slider>,
+    pub transparency_label: Adapted<Label>,
+    pub window_type_dd: Adapted<Dropdown>,
+    pub window_shape_dd: Adapted<Dropdown>,
+    pub enable_toggle: Adapted<Toggle>,
+    pub label24: Adapted<Label>,
+    pub width_spin: Adapted<Spinbox>,
+    pub height_spin: Adapted<Spinbox>,
+    pub surface_plate: Adapted<Plate>,
+    pub surface_info_label: Adapted<Label>,
+    pub surface_desc_label: Adapted<Label>,
+    pub range_slider_demo: Adapted<RangeSlider>,
+    pub trackpad_demo: Adapted<Trackpad>,
+    pub portal_panel: Adapted<Panel>,
+    pub portal_label: Adapted<Label>,
+    pub open_dialog_btn: Adapted<Button>,
+    pub save_dialog_btn: Adapted<Button>,
+    pub textbox_demo: Adapted<TextBox>,
+    pub plate_demo: Adapted<Plate>,
+    pub backplate_toggle: Adapted<Toggle>,
+    pub menubar_toggle: Adapted<Toggle>,
+    pub statusbar_toggle: Adapted<Toggle>,
+    pub border_section: Adapted<SectionContainer>,
+    pub bevel_toggle: Adapted<Toggle>,
+    pub border_width_spin: Adapted<Spinbox>,
+    pub bevel_depth_spin: Adapted<Spinbox>,
+    pub elements_section: Adapted<SectionContainer>,
+    pub page_selector: Adapted<Dropdown>,
+    pub color_ramp_btn: Adapted<Button>,
+    pub bevel_shape_btn: Adapted<Button>,
+    pub bevel_ramp: Adapted<Ramp>,
+    pub ramp_btn: Adapted<Button>,
+    pub control_panel: Adapted<ControlPanel>,
+    pub layout_dd: Adapted<Dropdown>,
+}
+
+pub const GALLERY_COUNT: usize = 53;
+
+impl GallerySlots {
+    pub fn get_dyn(&self, idx: usize) -> &(dyn Element + 'static) {
+        match idx {
+            0 => &self.menu_bar,
+            1 => &self.paginator,
+            2 => &self.status_bar,
+            3 => &self.button_demo,
+            4 => &self.label4,
+            5 => &self.label5,
+            6 => &self.label6,
+            7 => &self.label7,
+            8 => &self.label8,
+            9 => &self.checkbox_demo,
+            10 => &self.toggle_demo,
+            11 => &self.progress_demo,
+            12 => &self.slider_demo,
+            13 => &self.spinbox_demo,
+            14 => &self.panel_demo,
+            15 => &self.create_window_btn,
+            16 => &self.tile_windows_btn,
+            17 => &self.opacity_toggle,
+            18 => &self.label18,
+            19 => &self.transparency_slider,
+            20 => &self.transparency_label,
+            21 => &self.window_type_dd,
+            22 => &self.window_shape_dd,
+            23 => &self.enable_toggle,
+            24 => &self.label24,
+            25 => &self.width_spin,
+            26 => &self.height_spin,
+            27 => &self.surface_plate,
+            28 => &self.surface_info_label,
+            29 => &self.surface_desc_label,
+            30 => &self.range_slider_demo,
+            31 => &self.trackpad_demo,
+            32 => &self.portal_panel,
+            33 => &self.portal_label,
+            34 => &self.open_dialog_btn,
+            35 => &self.save_dialog_btn,
+            36 => &self.textbox_demo,
+            37 => &self.plate_demo,
+            38 => &self.backplate_toggle,
+            39 => &self.menubar_toggle,
+            40 => &self.statusbar_toggle,
+            41 => &self.border_section,
+            42 => &self.bevel_toggle,
+            43 => &self.border_width_spin,
+            44 => &self.bevel_depth_spin,
+            45 => &self.elements_section,
+            46 => &self.page_selector,
+            47 => &self.color_ramp_btn,
+            48 => &self.bevel_shape_btn,
+            49 => &self.bevel_ramp,
+            50 => &self.ramp_btn,
+            51 => &self.control_panel,
+            52 => &self.layout_dd,
+            _ => panic!("gallery slot index out of range: {idx}"),
+        }
+    }
+
+    pub fn get_dyn_mut(&mut self, idx: usize) -> &mut (dyn Element + 'static) {
+        match idx {
+            0 => &mut self.menu_bar,
+            1 => &mut self.paginator,
+            2 => &mut self.status_bar,
+            3 => &mut self.button_demo,
+            4 => &mut self.label4,
+            5 => &mut self.label5,
+            6 => &mut self.label6,
+            7 => &mut self.label7,
+            8 => &mut self.label8,
+            9 => &mut self.checkbox_demo,
+            10 => &mut self.toggle_demo,
+            11 => &mut self.progress_demo,
+            12 => &mut self.slider_demo,
+            13 => &mut self.spinbox_demo,
+            14 => &mut self.panel_demo,
+            15 => &mut self.create_window_btn,
+            16 => &mut self.tile_windows_btn,
+            17 => &mut self.opacity_toggle,
+            18 => &mut self.label18,
+            19 => &mut self.transparency_slider,
+            20 => &mut self.transparency_label,
+            21 => &mut self.window_type_dd,
+            22 => &mut self.window_shape_dd,
+            23 => &mut self.enable_toggle,
+            24 => &mut self.label24,
+            25 => &mut self.width_spin,
+            26 => &mut self.height_spin,
+            27 => &mut self.surface_plate,
+            28 => &mut self.surface_info_label,
+            29 => &mut self.surface_desc_label,
+            30 => &mut self.range_slider_demo,
+            31 => &mut self.trackpad_demo,
+            32 => &mut self.portal_panel,
+            33 => &mut self.portal_label,
+            34 => &mut self.open_dialog_btn,
+            35 => &mut self.save_dialog_btn,
+            36 => &mut self.textbox_demo,
+            37 => &mut self.plate_demo,
+            38 => &mut self.backplate_toggle,
+            39 => &mut self.menubar_toggle,
+            40 => &mut self.statusbar_toggle,
+            41 => &mut self.border_section,
+            42 => &mut self.bevel_toggle,
+            43 => &mut self.border_width_spin,
+            44 => &mut self.bevel_depth_spin,
+            45 => &mut self.elements_section,
+            46 => &mut self.page_selector,
+            47 => &mut self.color_ramp_btn,
+            48 => &mut self.bevel_shape_btn,
+            49 => &mut self.bevel_ramp,
+            50 => &mut self.ramp_btn,
+            51 => &mut self.control_panel,
+            52 => &mut self.layout_dd,
+            _ => panic!("gallery slot index out of range: {idx}"),
+        }
+    }
+}
+
+/// Child-window slots: the background and three of the five slots vary by runtime flags
+/// (`--type`, `--backplate`), so those are typed enums rather than fields.
+pub enum ChildBg {
+    Backplate(Adapted<Backplate>),
+    ContentBg(Adapted<ContentBg>),
+}
+
+pub enum ChildMain {
+    ColorRamp(Adapted<ColorRamp>),
+    Ramp(Adapted<Ramp>),
+    Desc(Adapted<Label>),
+}
+
+pub enum ChildAux3 {
+    Label(Adapted<Label>),
+    MenuBar(Adapted<MenuBar>),
+}
+
+pub enum ChildAux4 {
+    Label(Adapted<Label>),
+    StatusBar(Adapted<StatusBar>),
+}
+
+pub struct ChildSlots {
+    pub bg: ChildBg,
+    pub main: ChildMain,
+    pub close: Adapted<Button>,
+    pub aux3: ChildAux3,
+    pub aux4: ChildAux4,
+}
+
+pub const CHILD_COUNT: usize = 5;
+
+impl ChildSlots {
+    pub fn get_dyn(&self, idx: usize) -> &(dyn Element + 'static) {
+        match idx {
+            0 => match &self.bg {
+                ChildBg::Backplate(w) => w,
+                ChildBg::ContentBg(w) => w,
+            },
+            1 => match &self.main {
+                ChildMain::ColorRamp(w) => w,
+                ChildMain::Ramp(w) => w,
+                ChildMain::Desc(w) => w,
+            },
+            2 => &self.close,
+            3 => match &self.aux3 {
+                ChildAux3::Label(w) => w,
+                ChildAux3::MenuBar(w) => w,
+            },
+            4 => match &self.aux4 {
+                ChildAux4::Label(w) => w,
+                ChildAux4::StatusBar(w) => w,
+            },
+            _ => panic!("child slot index out of range: {idx}"),
+        }
+    }
+
+    pub fn get_dyn_mut(&mut self, idx: usize) -> &mut (dyn Element + 'static) {
+        match idx {
+            0 => match &mut self.bg {
+                ChildBg::Backplate(w) => w,
+                ChildBg::ContentBg(w) => w,
+            },
+            1 => match &mut self.main {
+                ChildMain::ColorRamp(w) => w,
+                ChildMain::Ramp(w) => w,
+                ChildMain::Desc(w) => w,
+            },
+            2 => &mut self.close,
+            3 => match &mut self.aux3 {
+                ChildAux3::Label(w) => w,
+                ChildAux3::MenuBar(w) => w,
+            },
+            4 => match &mut self.aux4 {
+                ChildAux4::Label(w) => w,
+                ChildAux4::StatusBar(w) => w,
+            },
+            _ => panic!("child slot index out of range: {idx}"),
+        }
+    }
+}
+
+/// The two roster modes. Boxed slot structs keep registered widget pointers stable while
+/// the containing `State` moves.
+pub enum Roster {
+    Gallery(Box<GallerySlots>),
+    Child(Box<ChildSlots>),
+}
+
+impl Roster {
+    pub fn len(&self) -> usize {
+        match self {
+            Roster::Gallery(_) => GALLERY_COUNT,
+            Roster::Child(_) => CHILD_COUNT,
+        }
+    }
+
+    pub fn get_dyn(&self, idx: usize) -> &(dyn Element + 'static) {
+        match self {
+            Roster::Gallery(s) => s.get_dyn(idx),
+            Roster::Child(s) => s.get_dyn(idx),
+        }
+    }
+
+    pub fn get_dyn_mut(&mut self, idx: usize) -> &mut (dyn Element + 'static) {
+        match self {
+            Roster::Gallery(s) => s.get_dyn_mut(idx),
+            Roster::Child(s) => s.get_dyn_mut(idx),
+        }
+    }
+
+    /// The gallery slots; panics in child mode (gallery-only paths assert their mode).
+    pub fn gallery(&self) -> &GallerySlots {
+        match self {
+            Roster::Gallery(s) => s,
+            Roster::Child(_) => panic!("gallery slots requested in child mode"),
+        }
+    }
+
+    pub fn gallery_mut(&mut self) -> &mut GallerySlots {
+        match self {
+            Roster::Gallery(s) => s,
+            Roster::Child(_) => panic!("gallery slots requested in child mode"),
+        }
+    }
+}
+
 struct State {
-    widgets: Vec<Box<dyn Element>>,
+    roster: Roster,
     positions: Vec<(f32, f32, f32, f32)>,
 
     status_text: String,
@@ -237,7 +548,8 @@ impl State {
         for i in 0..self.positions.len() {
             let visible = self.is_widget_visible(i);
             let pos = self.positions[i];
-            if let Some(widget) = self.widgets.get_mut(i) {
+            if i < self.roster.len() {
+                let widget = self.roster.get_dyn_mut(i);
                 if widget.is_dragging() {
                     continue;
                 }
@@ -271,16 +583,16 @@ impl State {
         
         if !self.is_child {
             let (x, y, w, h) = self.positions[51];
-            self.widgets[51].set_rect(x, y, w, h);
+            self.roster.get_dyn_mut(51).set_rect(x, y, w, h);
 
-            let sb_rect = self.widgets[2].rect();
+            let sb_rect = self.roster.get_dyn_mut(2).rect();
             let ddh = cce_ui::layout::dropdown_height();
             let pad_x = 10.0;
             let pad_y = (sb_rect.3 - ddh) / 2.0;
             let dw = 120.0;
             let dx = sb_rect.0 + sb_rect.2 - dw - pad_x;
             let dy = sb_rect.1 + pad_y;
-            self.widgets[46].set_rect(dx, dy, dw, ddh);
+            self.roster.get_dyn_mut(46).set_rect(dx, dy, dw, ddh);
         }
     }
 
@@ -356,7 +668,7 @@ impl cce_ui::engine::Application for State {
         let opacity = opacity || is_ramp_child;
         let status_text = "Select a test case to begin verification.".to_string();
 
-        let widgets: Vec<Box<dyn Element>> = if is_child {
+        let roster = if is_child {
             let desc_label = match child_type.as_deref() {
                 Some("Toplevel") => "This is an active simulated Toplevel window.".to_string(),
                 Some("Popup") => "This is an active simulated Popup window.".to_string(),
@@ -366,27 +678,24 @@ impl cce_ui::engine::Application for State {
                 Some(other) => format!("This is an active simulated {} window.", other),
                 None => "This is an active simulated window in the window manager.".to_string(),
             };
-            let bg: Box<dyn Element> = if use_backplate {
+            let bg = if use_backplate {
                 let mut bp = Backplate::new(0.0, 0.0, c_w, c_h);
                 if border_bevel {
                     bp.set_bevel(border_width);
                 }
-                Box::new(bp)
+                ChildBg::Backplate(bp)
             } else {
-                Box::new(ContentBg::new())
+                ChildBg::ContentBg(ContentBg::new())
             };
-            if child_type.as_deref() == Some("ColorRamp") {
-                vec![
-                    bg,
-                    Box::new(ColorRamp::new()),
-                    Box::new(Button::new(0.0, 0.0, 100.0, 35.0).with_label("Close")),
-                    Box::new(Label::new("").with_font_size(12.0)),
-                    Box::new(Label::new("").with_font_size(12.0)),
-                ]
+            let (main, aux3, aux4) = if child_type.as_deref() == Some("ColorRamp") {
+                (
+                    ChildMain::ColorRamp(ColorRamp::new()),
+                    ChildAux3::Label(Label::new("").with_font_size(12.0)),
+                    ChildAux4::Label(Label::new("").with_font_size(12.0)),
+                )
             } else if child_type.as_deref() == Some("Ramp") {
-                vec![
-                    bg,
-                    Box::new({
+                (
+                    ChildMain::Ramp({
                         let mut ramp = Ramp::new();
                         let (loaded_keys, loaded_type) = load_bevel_ramp();
                         ramp.keys = loaded_keys;
@@ -396,79 +705,80 @@ impl cce_ui::engine::Application for State {
                         };
                         ramp
                     }),
-                    Box::new(Button::new(0.0, 0.0, 100.0, 35.0).with_label("Close")),
-                    Box::new(Label::new("").with_font_size(12.0)),
-                    Box::new(Label::new("").with_font_size(12.0)),
-                ]
+                    ChildAux3::Label(Label::new("").with_font_size(12.0)),
+                    ChildAux4::Label(Label::new("").with_font_size(12.0)),
+                )
             } else {
                 let menu_bar = MenuBar::new(0.0, 0.0, c_w, 40.0)
                     .with_item("File", &["New", "Open", "Save", "Exit"])
                     .with_item("Edit", &["Undo", "Redo", "Cut", "Copy", "Paste"])
                     .with_right_aligned_title(true);
-
-                vec![
-                    bg,
-                    Box::new(Label::new(&desc_label).with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4])),
-                    Box::new(Button::new(0.0, 0.0, 100.0, 35.0).with_label("Close")),
-                    Box::new(menu_bar),
-                    Box::new(StatusBar::new()),
-                ]
-            }
+                (
+                    ChildMain::Desc(Label::new(&desc_label).with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4])),
+                    ChildAux3::MenuBar(menu_bar),
+                    ChildAux4::StatusBar(StatusBar::new()),
+                )
+            };
+            Roster::Child(Box::new(ChildSlots {
+                bg,
+                main,
+                close: Button::new(0.0, 0.0, 100.0, 35.0).with_label("Close"),
+                aux3,
+                aux4,
+            }))
         } else {
             let menu_bar = MenuBar::new(0.0, 0.0, c_w, 40.0)
                 .with_item("File", &["Exit"])
                 .with_item("Edit", &["Settings"])
                 .with_item("Help", &["About"])
                 .with_right_aligned_title(true);
-            vec![
-                Box::new(menu_bar), // 0
-                {
+            Roster::Gallery(Box::new(GallerySlots {
+                menu_bar: menu_bar,
+                paginator: {
                     let paginator = Paginator::new(vec![]);
-                    Box::new(paginator)
-                }, // 1
-                Box::new(StatusBar::new()), // 2
-                
-                Box::new(Button::new(0.0, 0.0, 140.0, 40.0).with_label("Button")), // 3
-                Box::new(Label::new("").with_font_size(12.0)), // 4
-                Box::new(Label::new("").with_font_size(12.0)), // 5
-                Box::new(Label::new("").with_font_size(12.0)), // 6
-                Box::new(Label::new("").with_font_size(12.0)), // 7
-                Box::new(Label::new("").with_font_size(12.0)), // 8
-                Box::new(Checkbox::new().with_label("Checkbox")), // 9
-                Box::new(Toggle::new().with_label("Toggle")), // 10
-                Box::new(ProgressBar::new(0.43).with_label("ProgressBar")), // 11
-                Box::new(Slider::new().with_label("Slider")), // 12
-                Box::new(Spinbox::new(10, 1, 100, 5).with_label("Spinbox")), // 13
-
-                Box::new(Panel::new(0.0, 0.0, 400.0, 250.0)), // 14
-                Box::new(Button::new(0.0, 0.0, 140.0, 40.0).with_label("Create Window")), // 15
-                Box::new(Button::new(0.0, 0.0, 140.0, 40.0).with_label("Tile Windows")), // 16
-                Box::new(Toggle::new().with_label("Opacity")), // 17
-                Box::new(Label::new("").with_font_size(12.0)), // 18
-                Box::new(Slider::new()), // 19
-                Box::new(Label::new("Transparency Level").with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4])), // 20
-                Box::new(Dropdown::new(vec![
+                    paginator
+                },
+                status_bar: StatusBar::new(),
+                button_demo: Button::new(0.0, 0.0, 140.0, 40.0).with_label("Button"),
+                label4: Label::new("").with_font_size(12.0),
+                label5: Label::new("").with_font_size(12.0),
+                label6: Label::new("").with_font_size(12.0),
+                label7: Label::new("").with_font_size(12.0),
+                label8: Label::new("").with_font_size(12.0),
+                checkbox_demo: Checkbox::new().with_label("Checkbox"),
+                toggle_demo: Toggle::new().with_label("Toggle"),
+                progress_demo: ProgressBar::new(0.43).with_label("ProgressBar"),
+                slider_demo: Slider::new().with_label("Slider"),
+                spinbox_demo: Spinbox::new(10, 1, 100, 5).with_label("Spinbox"),
+                panel_demo: Panel::new(0.0, 0.0, 400.0, 250.0),
+                create_window_btn: Button::new(0.0, 0.0, 140.0, 40.0).with_label("Create Window"),
+                tile_windows_btn: Button::new(0.0, 0.0, 140.0, 40.0).with_label("Tile Windows"),
+                opacity_toggle: Toggle::new().with_label("Opacity"),
+                label18: Label::new("").with_font_size(12.0),
+                transparency_slider: Slider::new(),
+                transparency_label: Label::new("Transparency Level").with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4]),
+                window_type_dd: Dropdown::new(vec![
                     "Toplevel".to_string(),
                     "Popup".to_string(),
                     "Layer: Top".to_string(),
                     "Layer: Overlay".to_string(),
                     "Layer: Background".to_string(),
-                ], 0).with_label("Window Type")), // 21
-                Box::new(Dropdown::new(vec![
+                ], 0).with_label("Window Type"),
+                window_shape_dd: Dropdown::new(vec![
                     "Rectangular".to_string(),
                     "Circular".to_string(),
-                ], 0).with_label("Window Shape")), // 22
-                Box::new({
+                ], 0).with_label("Window Shape"),
+                enable_toggle: {
                     let mut t = Toggle::new().with_label("Enable");
                     t.set_toggled(true);
                     t
-                }), // 23
-                Box::new(Label::new("").with_font_size(12.0)), // 24
-                Box::new(Spinbox::new(400, 100, 2000, 10).with_label("Width")), // 25
-                Box::new(Spinbox::new(250, 100, 2000, 10).with_label("Height")), // 26
-                Box::new(Plate::new(0.0, 0.0, 190.0, 250.0, true)), // 27
-                Box::new(Label::new("Surface Info").with_font_size(12.0)), // 28
-                Box::new(Label::new(
+                },
+                label24: Label::new("").with_font_size(12.0),
+                width_spin: Spinbox::new(400, 100, 2000, 10).with_label("Width"),
+                height_spin: Spinbox::new(250, 100, 2000, 10).with_label("Height"),
+                surface_plate: Plate::new(0.0, 0.0, 190.0, 250.0, true),
+                surface_info_label: Label::new("Surface Info").with_font_size(12.0),
+                surface_desc_label: Label::new(
                     "A standard application\n\
 window (xdg_toplevel).\n\
 It supports tiling (cascade,\n\
@@ -476,50 +786,49 @@ split, grid), fullscreening,\n\
 dragging, and resizing.\n\n\
 Testing layout:\n\
 cascades in cce."
-                ).with_font_size(10.0)), // 29
-                Box::new(RangeSlider::new().with_label("RangeSlider")), // 30
-                Box::new(Trackpad::new().with_label("Trackpad")), // 31
-                Box::new(Panel::new(0.0, 0.0, 450.0, 200.0).with_label("XDG Desktop Portal FileChooser")), // 32
-                Box::new(Label::new("This page verifies the integration of the XDG Desktop Portal\nFile Chooser in the Clear environment.").with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4])), // 33
-                Box::new(Button::new(0.0, 0.0, 180.0, 40.0).with_label("Open File Dialog")), // 34
-                Box::new(Button::new(0.0, 0.0, 180.0, 40.0).with_label("Save File Dialog")), // 35
-                
-                Box::new(TextBox::new("Interactive TextBox".to_string())), // 36
-                Box::new(Plate::new(0.0, 0.0, 120.0, 120.0, true).with_label("Plate")), // 37
-                Box::new({
+                ).with_font_size(10.0),
+                range_slider_demo: RangeSlider::new().with_label("RangeSlider"),
+                trackpad_demo: Trackpad::new().with_label("Trackpad"),
+                portal_panel: Panel::new(0.0, 0.0, 450.0, 200.0).with_label("XDG Desktop Portal FileChooser"),
+                portal_label: Label::new("This page verifies the integration of the XDG Desktop Portal\nFile Chooser in the Clear environment.").with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4]),
+                open_dialog_btn: Button::new(0.0, 0.0, 180.0, 40.0).with_label("Open File Dialog"),
+                save_dialog_btn: Button::new(0.0, 0.0, 180.0, 40.0).with_label("Save File Dialog"),
+                textbox_demo: TextBox::new("Interactive TextBox".to_string()),
+                plate_demo: Plate::new(0.0, 0.0, 120.0, 120.0, true).with_label("Plate"),
+                backplate_toggle: {
                     let mut t = Toggle::new().with_label("Backplate");
                     t.set_toggled(true);
                     t
-                }), // 38
-                Box::new({
+                },
+                menubar_toggle: {
                     let mut t = Toggle::new().with_label("MenuBar");
                     t.set_toggled(false);
                     t
-                }), // 39
-                Box::new({
+                },
+                statusbar_toggle: {
                     let mut t = Toggle::new().with_label("StatusBar");
                     t.set_toggled(false);
                     t
-                }), // 40
-                Box::new(SectionContainer::new("Border")), // 41
-                Box::new({
+                },
+                border_section: SectionContainer::new("Border"),
+                bevel_toggle: {
                     let mut t = Toggle::new().with_label("Bevel");
                     t.set_toggled(false);
                     t
-                }), // 42
-                Box::new(Spinbox::new(1, 1, 20, 1).with_label("Border Width")), // 43
-                Box::new({
+                },
+                border_width_spin: Spinbox::new(1, 1, 20, 1).with_label("Border Width"),
+                bevel_depth_spin: {
                     let default_depth = (cce_ui::layout::bevel_depth() * 100.0) as i32;
                     Spinbox::new(default_depth, 0, 100, 1).with_label("Bevel Depth")
-                }), // 44
-                Box::new(SectionContainer::new("Window Elements")), // 45
-                Box::new(Dropdown::new(vec!["Controls".to_string(), "Windows".to_string(), "XDG".to_string()], 0).with_open_upward(true)), // 46
-                Box::new(Button::new(0.0, 0.0, 120.0, 28.0).with_label("Color Ramp...")), // 47
-                Box::new(Button::new(0.0, 0.0, 120.0, 28.0).with_label("Bevel Shape...")), // 48
-                Box::new(Ramp::new()), // 49
-                Box::new(Button::new(0.0, 0.0, 120.0, 28.0).with_label("Ramp...")), // 50
-                Box::new(ControlPanel::new().with_label("ControlPanel")), // 51
-                Box::new(Dropdown::new(vec![
+                },
+                elements_section: SectionContainer::new("Window Elements"),
+                page_selector: Dropdown::new(vec!["Controls".to_string(), "Windows".to_string(), "XDG".to_string()], 0).with_open_upward(true),
+                color_ramp_btn: Button::new(0.0, 0.0, 120.0, 28.0).with_label("Color Ramp..."),
+                bevel_shape_btn: Button::new(0.0, 0.0, 120.0, 28.0).with_label("Bevel Shape..."),
+                bevel_ramp: Ramp::new(),
+                ramp_btn: Button::new(0.0, 0.0, 120.0, 28.0).with_label("Ramp..."),
+                control_panel: ControlPanel::new().with_label("ControlPanel"),
+                layout_dd: Dropdown::new(vec![
                     "Vertical".to_string(),
                     "Columns".to_string(),
                     "Grid".to_string(),
@@ -531,23 +840,23 @@ cascades in cce."
                     "Circular Pane".to_string(),
                     "Mosaic".to_string(),
                     "Reverse Mosaic".to_string(),
-                ], 9).with_label("Layout")), // 52
-            ]
+                ], 9).with_label("Layout"),
+            }))
         };
 
         let positions = if is_child {
             child_positions(c_w, c_h, use_backplate, use_menubar, use_statusbar, child_type.as_deref())
         } else {
             let sidebar_w = cce_ui::widget::PageSelector::sidebar_w(
-                widgets[1].as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("widgets[1] must be the Paginator"),
+                roster.gallery().paginator.as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("slot 1 must be the Paginator"),
             );
-            demo_positions(c_w, c_h, sidebar_w, 9, &widgets)
+            demo_positions(c_w, c_h, sidebar_w, 9, roster.gallery())
         };
 
         let (loaded_keys, loaded_type) = load_bevel_ramp();
 
         let mut state = Self {
-            widgets,
+            roster,
             positions,
             status_text,
             drag_widget: None,
@@ -580,21 +889,21 @@ cascades in cce."
         if !is_child {
             let child_ptrs: Vec<*mut (dyn Element + 'static)> = [15, 16, 21, 22, 17, 19, 20, 23, 25, 26, 38, 39, 40, 41, 42, 43, 44, 45, 48]
                 .iter()
-                .map(|&idx| state.widgets[idx].as_ptr_mut())
+                .map(|&idx| state.roster.get_dyn_mut(idx).as_ptr_mut())
                 .collect();
-            let cp = state.widgets[51].as_any_mut().downcast_mut::<ControlPanel>().expect("widget 51 must be a ControlPanel");
+            let cp = state.roster.get_dyn_mut(51).as_any_mut().downcast_mut::<ControlPanel>().expect("widget 51 must be a ControlPanel");
             for ptr in child_ptrs {
                 cp.add_child(ptr);
             }
 
             // Set page selector (46) parent to StatusBar (2)
-            let statusbar_ptr = state.widgets[2].as_ptr_mut();
-            state.widgets[46].set_parent(Some(statusbar_ptr), &mut state.ui_context);
-            let dropdown_ptr = state.widgets[46].as_ptr_mut();
-            state.widgets[2].add_child(dropdown_ptr, &mut state.ui_context);
+            let statusbar_ptr = state.roster.get_dyn_mut(2).as_ptr_mut();
+            state.roster.get_dyn_mut(46).set_parent(Some(statusbar_ptr), &mut state.ui_context);
+            let dropdown_ptr = state.roster.get_dyn_mut(46).as_ptr_mut();
+            state.roster.get_dyn_mut(2).add_child(dropdown_ptr, &mut state.ui_context);
         } else {
             state.focused_widget = Some(1);
-            let ramp = state.widgets[1].as_any_mut().downcast_mut::<Ramp>().expect("child ramp widget");
+            let ramp = state.roster.get_dyn_mut(1).as_any_mut().downcast_mut::<Ramp>().expect("child ramp widget");
             let preset_ptr = ramp.preset_dropdown.as_ptr_mut();
             state.ui_context.set_focused_ptr(preset_ptr);
             unsafe {
@@ -701,7 +1010,8 @@ cascades in cce."
                 }
             }
         };
-        for (i, w) in self.widgets.iter_mut().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
             if is_visible(i) {
                 if is_control_panel_child(i) {
                     continue;
@@ -743,9 +1053,9 @@ cascades in cce."
                 child_positions(self.width, self.height, self.use_backplate, self.use_menubar, self.use_statusbar, self.child_type.as_deref())
             } else {
                 let sidebar_w = cce_ui::widget::PageSelector::sidebar_w(
-                    self.widgets[1].as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("widgets[1] must be the Paginator"),
+                    self.roster.get_dyn_mut(1).as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("widgets[1] must be the Paginator"),
                 );
-                demo_positions(self.width, self.height, sidebar_w, self.layout_idx, &self.widgets)
+                demo_positions(self.width, self.height, sidebar_w, self.layout_idx, self.roster.gallery())
             };
             self.apply_layout();
             let text = self.status_text.clone();
@@ -772,7 +1082,8 @@ cascades in cce."
             }
         };
 
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -781,10 +1092,10 @@ cascades in cce."
             }
             if !self.is_child && i == 14 {
                 let r = cce_ui::color::backplate_corner_radius();
-                let backplate_enabled = self.widgets[38].get_value_string() == Some("true".to_string());
-                let border_bevel = self.widgets[42].get_value_string() == Some("true".to_string());
-                let opacity_enabled = self.widgets[17].get_value_string() == Some("true".to_string());
-                let transparency_val = if opacity_enabled { self.widgets[19].value() as f32 / 100.0 } else { 1.0 };
+                let backplate_enabled = self.roster.get_dyn(38).get_value_string() == Some("true".to_string());
+                let border_bevel = self.roster.get_dyn(42).get_value_string() == Some("true".to_string());
+                let opacity_enabled = self.roster.get_dyn(17).get_value_string() == Some("true".to_string());
+                let transparency_val = if opacity_enabled { self.roster.get_dyn(19).value() as f32 / 100.0 } else { 1.0 };
                 let bg_color = if backplate_enabled {
                     let mut col = cce_ui::color::page_low_color();
                     col[3] = transparency_val;
@@ -796,7 +1107,7 @@ cascades in cce."
                 let (wx, wy, ww, wh) = w.rect();
                 if backplate_enabled {
                     if border_bevel {
-                        let t = self.widgets[43].value() as f32;
+                        let t = self.roster.get_dyn(43).value() as f32;
                         let r_inner = (r - t).max(0.0);
                         push_rounded(&mut pc, wx + t, wy + t, ww - 2.0 * t, wh - 2.0 * t, r_inner, bg_color, (true, true, true, true));
                     } else {
@@ -804,7 +1115,7 @@ cascades in cce."
                     }
                 }
 
-                let menubar_enabled = self.widgets[39].get_value_string() == Some("true".to_string());
+                let menubar_enabled = self.roster.get_dyn(39).get_value_string() == Some("true".to_string());
                 if menubar_enabled {
                     let mut menu_color = cce_ui::color::backplate_menubar_color();
                     menu_color[3] = transparency_val;
@@ -815,7 +1126,7 @@ cascades in cce."
                     }
                 }
 
-                let statusbar_enabled = self.widgets[40].get_value_string() == Some("true".to_string());
+                let statusbar_enabled = self.roster.get_dyn(40).get_value_string() == Some("true".to_string());
                 if statusbar_enabled {
                     let mut status_color = cce_ui::color::backplate_statusbar_color();
                     status_color[3] = transparency_val;
@@ -842,7 +1153,8 @@ cascades in cce."
         }
 
         // ── Plain geometry (the legacy view() body) ──
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -850,9 +1162,9 @@ cascades in cce."
                 continue;
             }
             if !self.is_child && i == 14 {
-                let backplate_enabled = self.widgets[38].get_value_string() == Some("true".to_string());
-                let opacity_enabled = self.widgets[17].get_value_string() == Some("true".to_string());
-                let transparency_val = if opacity_enabled { self.widgets[19].value() as f32 / 100.0 } else { 1.0 };
+                let backplate_enabled = self.roster.get_dyn(38).get_value_string() == Some("true".to_string());
+                let opacity_enabled = self.roster.get_dyn(17).get_value_string() == Some("true".to_string());
+                let transparency_val = if opacity_enabled { self.roster.get_dyn(19).value() as f32 / 100.0 } else { 1.0 };
                 let bg_color = if backplate_enabled {
                     let mut col = cce_ui::color::page_low_color();
                     col[3] = transparency_val;
@@ -873,7 +1185,8 @@ cascades in cce."
 
         // ── Popovers: geometry then labels, in-frame, on top of everything ──
         let mut popover_pc = cce_ui::layout::PopoverCollector::new();
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -907,7 +1220,8 @@ cascades in cce."
             }
         };
         let mut has_menu_bar = false;
-        for w in &mut self.widgets {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
             if let Some(menu_bar) = w.as_any_mut().downcast_mut::<MenuBar>() {
                 menu_bar.title = label_text.clone();
                 has_menu_bar = true;
@@ -918,7 +1232,8 @@ cascades in cce."
         }
 
         let mut popover_rects = Vec::new();
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -935,7 +1250,8 @@ cascades in cce."
             false
         };
 
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -952,7 +1268,7 @@ cascades in cce."
                 continue;
             }
             let mut scratch = cce_ui::scene::paint::PaintCtx::new();
-            cce_ui::scene::painter::append_widget_text(&self.ui_context, w.as_ref(), &mut scratch);
+            cce_ui::scene::painter::append_widget_text(&self.ui_context, w, &mut scratch);
             for item in scratch.finish().items {
                 if let cce_ui::scene::paint::Prim::Text { text, x, y, font_size, color, font, bounds, .. } = item.prim {
                     if in_any_popover(x, y) {
@@ -1010,7 +1326,8 @@ cascades in cce."
         let sw = size.width as f32;
         let sh = size.height as f32;
 
-        for (i, w) in self.widgets.iter().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn(i);
             if !self.is_widget_visible(i) {
                 continue;
             }
@@ -1019,11 +1336,11 @@ cascades in cce."
             }
 
             if !self.is_child && i == 14 {
-                let border_enabled = self.widgets[23].get_value_string() == Some("true".to_string());
+                let border_enabled = self.roster.get_dyn(23).get_value_string() == Some("true".to_string());
                 if border_enabled {
-                    let backplate_enabled = self.widgets[38].get_value_string() == Some("true".to_string());
-                    let opacity_enabled = self.widgets[17].get_value_string() == Some("true".to_string());
-                    let transparency_val = if opacity_enabled { self.widgets[19].value() as f32 / 100.0 } else { 1.0 };
+                    let backplate_enabled = self.roster.get_dyn(38).get_value_string() == Some("true".to_string());
+                    let opacity_enabled = self.roster.get_dyn(17).get_value_string() == Some("true".to_string());
+                    let transparency_val = if opacity_enabled { self.roster.get_dyn(19).value() as f32 / 100.0 } else { 1.0 };
                     let bg_color = if backplate_enabled {
                         let mut col = cce_ui::color::page_low_color();
                         col[3] = transparency_val;
@@ -1034,8 +1351,8 @@ cascades in cce."
 
                     let mut border_color = cce_ui::color::plate_border_color().unwrap_or([0.3, 0.3, 0.4, 1.0]);
                     border_color[3] = transparency_val;
-                    let t = self.widgets[43].value() as f32;
-                    let border_bevel = self.widgets[42].get_value_string() == Some("true".to_string());
+                    let t = self.roster.get_dyn(43).value() as f32;
+                    let border_bevel = self.roster.get_dyn(42).get_value_string() == Some("true".to_string());
                     let r = cce_ui::color::backplate_corner_radius();
                     let (wx, wy, ww, wh) = w.rect();
                     
@@ -1052,7 +1369,7 @@ cascades in cce."
                                 let h_inner = interpolate_ramp_value(&self.bevel_ramp, u_next, &self.bevel_ramp_line_type);
                                 
                                 let d_h = h_inner - h_outer;
-                                let bevel_depth = self.widgets[44].value() as f32 / 100.0;
+                                let bevel_depth = self.roster.get_dyn(44).value() as f32 / 100.0;
                                 let color_offset = d_h * bevel_depth * 2.667;
                                 
                                  let rad = cce_ui::layout::light_source_position();
@@ -1136,7 +1453,7 @@ cascades in cce."
 
         let mut changed = false;
         if let Some(idx) = self.drag_widget {
-            if self.widgets[idx].drag_update(lx, ly) {
+            if self.roster.get_dyn_mut(idx).drag_update(lx, ly) {
                 changed = true;
             }
         }
@@ -1163,7 +1480,8 @@ cascades in cce."
                     }
                 }
             };
-            for (i, w) in self.widgets.iter_mut().enumerate() {
+            for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
                 if !is_visible(i) {
                     continue;
                 }
@@ -1211,14 +1529,14 @@ cascades in cce."
 
         if state == ElementState::Pressed {
             let mut clicked_idx = None;
-            for i in (0..self.widgets.len()).rev() {
+            for i in (0..self.roster.len()).rev() {
                 if !is_visible(i) {
                     continue;
                 }
                 if is_control_panel_child(i) {
                     continue;
                 }
-                if self.widgets[i].hit_test(lx, ly, &self.ui_context) {
+                if self.roster.get_dyn_mut(i).hit_test(lx, ly, &self.ui_context) {
                     clicked_idx = Some(i);
                     break;
                 }
@@ -1226,34 +1544,35 @@ cascades in cce."
             if button == MouseButton::Left {
                 if let Some(old) = self.focused_widget {
                     if Some(old) != clicked_idx {
-                        self.widgets[old].unfocus();
+                        self.roster.get_dyn_mut(old).unfocus();
                         self.focused_widget = None;
                     }
                 }
             }
             if let Some(i) = clicked_idx {
-                if self.widgets[i].mouse_input(button, state, lx, ly, &mut self.ui_context) {
+                if self.roster.get_dyn_mut(i).mouse_input(button, state, lx, ly, &mut self.ui_context) {
                     changed = true;
                 }
-                if button == MouseButton::Left && self.widgets[i].draggable() {
-                    self.widgets[i].drag_begin(lx, ly);
+                if button == MouseButton::Left && self.roster.get_dyn_mut(i).draggable() {
+                    self.roster.get_dyn_mut(i).drag_begin(lx, ly);
                     self.drag_widget = Some(i);
                 }
                 if button == MouseButton::Left {
-                    self.widgets[i].focus();
+                    self.roster.get_dyn_mut(i).focus();
                     self.focused_widget = Some(i);
                 }
             }
         } else {
             if button == MouseButton::Left {
                 if let Some(idx) = self.drag_widget {
-                    self.widgets[idx].drag_end();
+                    self.roster.get_dyn_mut(idx).drag_end();
                     self.drag_widget = None;
                     changed = true;
                 }
             }
 
-            for (i, w) in self.widgets.iter_mut().enumerate() {
+            for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
                 if !is_visible(i) {
                     continue;
                 }
@@ -1267,14 +1586,14 @@ cascades in cce."
 
             if button == MouseButton::Left {
                 if self.is_child {
-                    if self.widgets[2].take_click() {
+                    if self.roster.get_dyn_mut(2).take_click() {
                         return Some("exit".to_string());
                     }
                 } else {
                     let mut page_changed = false;
                     let mut selected = 0;
-                    if self.widgets[46].take_click() {
-                        selected = self.widgets[46].value();
+                    if self.roster.get_dyn_mut(46).take_click() {
+                        selected = self.roster.get_dyn(46).value();
                         page_changed = true;
                     }
                     
@@ -1291,15 +1610,15 @@ cascades in cce."
                         }
                         self.apply_layout();
                         changed = true;
-                    } else if self.widgets[52].take_click() {
-                        self.layout_idx = self.widgets[52].value() as usize;
-                        self.positions = demo_positions(self.width, self.height, cce_ui::widget::PageSelector::sidebar_w(self.widgets[1].as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("widgets[1] must be the Paginator")), self.layout_idx, &self.widgets);
+                    } else if self.roster.get_dyn_mut(52).take_click() {
+                        self.layout_idx = self.roster.get_dyn(52).value() as usize;
+                        self.positions = demo_positions(self.width, self.height, cce_ui::widget::PageSelector::sidebar_w(self.roster.gallery().paginator.as_any().downcast_ref::<cce_ui::widget::Paginator>().expect("slot 1 must be the Paginator")), self.layout_idx, self.roster.gallery());
                         self.apply_layout();
                         changed = true;
                     } else if self.current_page == Page::Controls {
-                        if self.widgets[3].take_click() {
+                        if self.roster.get_dyn_mut(3).take_click() {
                             // Does nothing
-                        } else if self.widgets[47].take_click() {
+                        } else if self.roster.get_dyn_mut(47).take_click() {
                             if let Ok(exe) = std::env::current_exe() {
                                 let mut cmd = std::process::Command::new(exe);
                                 cmd.arg("--child")
@@ -1312,7 +1631,7 @@ cascades in cce."
                                    .arg("--backplate");
                                 let _ = cmd.spawn();
                             }
-                        } else if self.widgets[50].take_click() {
+                        } else if self.roster.get_dyn_mut(50).take_click() {
                             if let Ok(exe) = std::env::current_exe() {
                                 let mut cmd = std::process::Command::new(exe);
                                 cmd.arg("--child")
@@ -1327,7 +1646,7 @@ cascades in cce."
                             }
                         } else {
                             for i in [9, 10, 12, 13, 30, 31, 36, 37] {
-                                if self.widgets[i].take_click() {
+                                if self.roster.get_dyn_mut(i).take_click() {
                                     changed = true;
                                 }
                             }
@@ -1336,11 +1655,11 @@ cascades in cce."
                         let mut create_window = false;
                         let mut tile_windows = false;
                         
-                        if self.widgets[15].take_click() {
+                        if self.roster.get_dyn_mut(15).take_click() {
                             create_window = true;
-                        } else if self.widgets[16].take_click() {
+                        } else if self.roster.get_dyn_mut(16).take_click() {
                             tile_windows = true;
-                        } else if self.widgets[48].take_click() {
+                        } else if self.roster.get_dyn_mut(48).take_click() {
                             if let Ok(exe) = std::env::current_exe() {
                                 let mut cmd = std::process::Command::new(exe);
                                 cmd.arg("--child")
@@ -1356,13 +1675,13 @@ cascades in cce."
                         } else {
                             let mut dropdown_clicked = false;
                             for i in [17, 19, 21, 22, 23, 25, 26, 38, 39, 40, 42, 43, 44] {
-                                if self.widgets[i].take_click() {
+                                if self.roster.get_dyn_mut(i).take_click() {
                                     changed = true;
                                     if i == 21 {
                                         dropdown_clicked = true;
                                     }
                                     if i == 44 {
-                                        let depth = self.widgets[44].value() as f32 / 100.0;
+                                        let depth = self.roster.get_dyn(44).value() as f32 / 100.0;
                                         if let Ok(mut registry) = cce_ui::layout::get_style_registry().write() {
                                             registry.set_float("bevel_depth", depth);
                                         }
@@ -1370,7 +1689,7 @@ cascades in cce."
                                 }
                             }
                             if dropdown_clicked {
-                                let desc = match self.widgets[21].value() {
+                                let desc = match self.roster.get_dyn(21).value() {
                                     0 => "A standard application\n\
 window (xdg_toplevel).\n\
 It supports tiling (cascade,\n\
@@ -1408,12 +1727,12 @@ Testing layout:\n\
 full screen background.",
                                     _ => "",
                                 };
-                                self.widgets[29].set_text(desc);
+                                self.roster.get_dyn_mut(29).set_text(desc);
                             }
                         }
                         
                         if create_window {
-                            let window_type = match self.widgets[21].value() {
+                            let window_type = match self.roster.get_dyn(21).value() {
                                 0 => "Toplevel",
                                 1 => "Popup",
                                 2 => "LayerTop",
@@ -1421,17 +1740,17 @@ full screen background.",
                                 4 => "LayerBackground",
                                 _ => "Toplevel",
                             };
-                            let shape = match self.widgets[22].value() {
+                            let shape = match self.roster.get_dyn(22).value() {
                                 0 => "rectangular",
                                 1 => "circular",
                                 _ => "rectangular",
                             };
-                            let opacity_enabled = self.widgets[17].get_value_string() == Some("true".to_string());
-                            let transparency_pct = self.widgets[19].value();
+                            let opacity_enabled = self.roster.get_dyn(17).get_value_string() == Some("true".to_string());
+                            let transparency_pct = self.roster.get_dyn(19).value();
                             let transparency_val = transparency_pct as f32 / 100.0;
-                            let border_enabled = self.widgets[23].get_value_string() == Some("true".to_string());
-                            let custom_width = self.widgets[25].value();
-                            let custom_height = self.widgets[26].value();
+                            let border_enabled = self.roster.get_dyn(23).get_value_string() == Some("true".to_string());
+                            let custom_width = self.roster.get_dyn(25).value();
+                            let custom_height = self.roster.get_dyn(26).value();
      
                             self.update_status_text(&format!("Spawning simulated {} {} window...", shape, window_type));
                             if let Ok(exe) = std::env::current_exe() {
@@ -1441,9 +1760,9 @@ full screen background.",
                                    .arg(window_type)
                                    .arg("--shape")
                                    .arg(shape);
-                                let backplate_enabled = self.widgets[38].get_value_string() == Some("true".to_string());
-                                let menubar_enabled = self.widgets[39].get_value_string() == Some("true".to_string());
-                                let statusbar_enabled = self.widgets[40].get_value_string() == Some("true".to_string());
+                                let backplate_enabled = self.roster.get_dyn(38).get_value_string() == Some("true".to_string());
+                                let menubar_enabled = self.roster.get_dyn(39).get_value_string() == Some("true".to_string());
+                                let statusbar_enabled = self.roster.get_dyn(40).get_value_string() == Some("true".to_string());
                                 if opacity_enabled {
                                     cmd.arg("--opacity")
                                        .arg("--transparency")
@@ -1452,8 +1771,8 @@ full screen background.",
                                 if !border_enabled {
                                     cmd.arg("--no-border");
                                 } else {
-                                    let border_width = self.widgets[43].value() as f32;
-                                    let border_bevel = self.widgets[42].get_value_string() == Some("true".to_string());
+                                    let border_width = self.roster.get_dyn(43).value() as f32;
+                                    let border_bevel = self.roster.get_dyn(42).get_value_string() == Some("true".to_string());
                                     cmd.arg("--border-width")
                                        .arg(border_width.to_string());
                                     if border_bevel {
@@ -1483,9 +1802,9 @@ full screen background.",
                     } else if self.current_page == Page::Xdg {
                         let mut open_file = false;
                         let mut save_file = false;
-                        if self.widgets[34].take_click() {
+                        if self.roster.get_dyn_mut(34).take_click() {
                             open_file = true;
-                        } else if self.widgets[35].take_click() {
+                        } else if self.roster.get_dyn_mut(35).take_click() {
                             save_file = true;
                         }
  
@@ -1540,7 +1859,8 @@ full screen background.",
                 }
             }
         };
-        for (i, w) in self.widgets.iter_mut().enumerate() {
+        for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
             if !is_visible(i) {
                 continue;
             }
@@ -1582,14 +1902,15 @@ full screen background.",
         };
         let mut handled = false;
         if let Some(focused) = self.focused_widget {
-            if self.widgets[focused].keyboard_input(event, &mut self.ui_context) {
+            if self.roster.get_dyn_mut(focused).keyboard_input(event, &mut self.ui_context) {
                 changed = true;
                 handled = true;
             }
         }
 
         if !handled {
-            for (i, w) in self.widgets.iter_mut().enumerate() {
+            for i in 0..self.roster.len() {
+            let w = self.roster.get_dyn_mut(i);
                 if Some(i) == self.focused_widget {
                     continue;
                 }
@@ -1661,7 +1982,7 @@ full screen background.",
     }
 }
 
-fn demo_positions(sw: f32, sh: f32, sidebar_w: f32, layout_idx: usize, widgets: &[Box<dyn Element>]) -> Vec<(f32, f32, f32, f32)> {
+fn demo_positions(sw: f32, sh: f32, sidebar_w: f32, layout_idx: usize, slots: &GallerySlots) -> Vec<(f32, f32, f32, f32)> {
     let base_x = sidebar_w + 20.0;
     let sph = cce_ui::layout::spinbox_height();
     let tgh = cce_ui::layout::toggle_height();
@@ -1794,7 +2115,7 @@ fn demo_positions(sw: f32, sh: f32, sidebar_w: f32, layout_idx: usize, widgets:
     }
 
     let label_off = |idx: usize| -> f32 {
-        cce_ui::widget::label_offset(widgets[idx].as_ref())
+        cce_ui::widget::label_offset(slots.get_dyn(idx))
     };
 
     let mut items: Vec<(usize, f32, f32, f32)> = vec![