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

commit622d7b8740c54ac6fad7b5785ef3fd7c49834546
parentc251b6f69e
authorLucas Galante <[email protected]>
date2026-07-10 15:28
refactor(widget): delete dead SplitBox / MultiControl / KeybindsControl — no app constructs them

Phase 6 endgame, toward the per-widget text-getter deletion. These three widgets
are orphaned — Phase 6's container dissolutions left no client app (nor any live
cce-ui code path) constructing them:

- SplitBox — apps own their split panes now (files, data-editor dissolved it);
  only re-exports remained.
- MultiControl / KeybindsControl — zero app references; reachable only via
  `render_widget` / `window_runner` downcasts for per-sub-widget corner
  resolution, which fire only when the widget IS one of these types. No live
  widget is, so removing the branches is behavior-preserving.

Removed: the three source files, their re-exports (input/mod, container/mod,
widget/mod), the two `get_*_sub_widget_info` helpers + the render_widget/
window_runner downcast blocks that called them, the now-dead `name ==
"MultiControl"/"KeybindsControl"` span-full terms, and render_widget's redundant
`let mut corners = corners; …` shadows (the deleted blocks were their only
mutators).

Each of these carried the full legacy text-getter aggregation
(text_labels / text_labels_with_bounds / text_labels_with_font_and_bounds /
get_text_items), so this trims a sizable slice of the getter consumer graph that
the eventual getter deletion has to clear — the getters themselves can't come out
until cce-designer, settings' render_widget, and the four hand-aggregate apps
(email, authenticator, display-manager, layout-interface) move off them.

cce-ui + all 18 client apps compile; 175 tests pass (down from 180: the deleted
files carried 5 unit tests; the lone graph flake passes in isolation). Settings
(the heaviest render_widget user) live A/B AE=0 — pixel-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/backend/window_runner.rs         |   49 --
 src/layout.rs                        |  147 +----
 src/widget/container/mod.rs          |    2 -
 src/widget/container/splitbox.rs     |  475 --------------
 src/widget/input/keybinds_control.rs |  394 -----------
 src/widget/input/mod.rs              |    4 -
 src/widget/input/multi_control.rs    | 1193 ----------------------------------
 src/widget/mod.rs                    |    6 +-
 8 files changed, 4 insertions(+), 2266 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index bbfd25d..9c1c5c1 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1194,55 +1194,6 @@ fn get_child_widget_for_quad<'a>(
             }
         }
     }
-    if let Some(mc) = w.as_any().downcast_ref::<crate::widget::input::MultiControl>() {
-        let (bx, by, bw, bh) = mc.add_button.rect();
-        if qx >= bx - 0.1 && qx + qw <= bx + bw + 0.1 && qy >= by - 0.1 && qy + qh <= by + bh + 0.1 {
-            return &mc.add_button;
-        }
-        for row in &mc.rows {
-            let (kx, ky, kw, kh) = row.key_input.rect();
-            if qx >= kx - 0.1 && qx + qw <= kx + kw + 0.1 && qy >= ky - 0.1 && qy + qh <= ky + kh + 0.1 {
-                return &row.key_input;
-            }
-            let (tx, ty, tw, th) = row.type_dropdown.rect();
-            if qx >= tx - 0.1 && qx + qw <= tx + tw + 0.1 && qy >= ty - 0.1 && qy + qh <= ty + th + 0.1 {
-                return &row.type_dropdown;
-            }
-            let (rx, ry, rw, rh) = row.remove_button.rect();
-            if qx >= rx - 0.1 && qx + qw <= rx + rw + 0.1 && qy >= ry - 0.1 && qy + qh <= ry + rh + 0.1 {
-                return &row.remove_button;
-            }
-            let (vx, vy, vw, vh) = row.value_widget.rect();
-            if qx >= vx - 0.1 && qx + qw <= vx + vw + 0.1 && qy >= vy - 0.1 && qy + qh <= vy + vh + 0.1 {
-                return match &row.value_widget {
-                    crate::widget::input::InstancedWidget::TextBox(w) => w,
-                    crate::widget::input::InstancedWidget::Spinbox(w) => w,
-                    crate::widget::input::InstancedWidget::Toggle(w) => w,
-                    crate::widget::input::InstancedWidget::Slider(w) => w,
-                };
-            }
-        }
-    }
-    if let Some(kc) = w.as_any().downcast_ref::<crate::widget::input::KeybindsControl>() {
-        let (bx, by, bw, bh) = kc.add_button.rect();
-        if qx >= bx - 0.1 && qx + qw <= bx + bw + 0.1 && qy >= by - 0.1 && qy + qh <= by + bh + 0.1 {
-            return &kc.add_button;
-        }
-        for row in &kc.rows {
-            let (kx, ky, kw, kh) = row.key_input.rect();
-            if qx >= kx - 0.1 && qx + qw <= kx + kw + 0.1 && qy >= ky - 0.1 && qy + qh <= ky + kh + 0.1 {
-                return &row.key_input;
-            }
-            let (cx, cy, cw, ch) = row.cmd_input.rect();
-            if qx >= cx - 0.1 && qx + qw <= cx + cw + 0.1 && qy >= cy - 0.1 && qy + qh <= cy + ch + 0.1 {
-                return &row.cmd_input;
-            }
-            let (rx, ry, rw, rh) = row.remove_button.rect();
-            if qx >= rx - 0.1 && qx + qw <= rx + rw + 0.1 && qy >= ry - 0.1 && qy + qh <= ry + rh + 0.1 {
-                return &row.remove_button;
-            }
-        }
-    }
     w
 }
 
diff --git a/src/layout.rs b/src/layout.rs
index 62044bc..7cedd73 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3140,116 +3140,6 @@ impl RenderTarget for PopoverCollector {
     }
 }
 
-fn get_multicontrol_sub_widget_info(
-    mc: &crate::widget::input::MultiControl,
-    qx: f32, qy: f32, qw: f32, qh: f32,
-) -> Option<((bool, bool, bool, bool), f32, (f32, f32, f32, f32), f32)> {
-    // Check add_button
-    let (bx, by, bw, bh) = mc.add_button.rect();
-    if qx >= bx - 0.1 && qx + qw <= bx + bw + 0.1 && qy >= by - 0.1 && qy + qh <= by + bh + 0.1 {
-        return Some((
-            mc.add_button.rounded_corners(),
-            mc.add_button.corner_radius(),
-            (bx, by, bw, bh),
-            crate::widget::label_offset(&mc.add_button),
-        ));
-    }
-    // Check rows
-    for row in &mc.rows {
-        // key_input
-        let (kx, ky, kw, kh) = row.key_input.rect();
-        if qx >= kx - 0.1 && qx + qw <= kx + kw + 0.1 && qy >= ky - 0.1 && qy + qh <= ky + kh + 0.1 {
-            return Some((
-                row.key_input.rounded_corners(),
-                row.key_input.corner_radius(),
-                (kx, ky, kw, kh),
-                crate::widget::label_offset(&row.key_input),
-            ));
-        }
-        // type_dropdown
-        let (tx, ty, tw, th) = row.type_dropdown.rect();
-        if qx >= tx - 0.1 && qx + qw <= tx + tw + 0.1 && qy >= ty - 0.1 && qy + qh <= ty + th + 0.1 {
-            return Some((
-                row.type_dropdown.rounded_corners(),
-                row.type_dropdown.corner_radius(),
-                (tx, ty, tw, th),
-                crate::widget::label_offset(&row.type_dropdown),
-            ));
-        }
-        // remove_button
-        let (rx, ry, rw, rh) = row.remove_button.rect();
-        if qx >= rx - 0.1 && qx + qw <= rx + rw + 0.1 && qy >= ry - 0.1 && qy + qh <= ry + rh + 0.1 {
-            return Some((
-                row.remove_button.rounded_corners(),
-                row.remove_button.corner_radius(),
-                (rx, ry, rw, rh),
-                crate::widget::label_offset(&row.remove_button),
-            ));
-        }
-        // value_widget
-        let (vx, vy, vw, vh) = row.value_widget.rect();
-        if qx >= vx - 0.1 && qx + qw <= vx + vw + 0.1 && qy >= vy - 0.1 && qy + qh <= vy + vh + 0.1 {
-            let (corners, radius, label_offset) = match &row.value_widget {
-                crate::widget::input::InstancedWidget::TextBox(w) => (w.rounded_corners(), w.corner_radius(), crate::widget::label_offset(w)),
-                crate::widget::input::InstancedWidget::Spinbox(w) => (w.rounded_corners(), w.corner_radius(), crate::widget::label_offset(w)),
-                crate::widget::input::InstancedWidget::Toggle(w) => (w.rounded_corners(), w.corner_radius(), crate::widget::label_offset(w)),
-                crate::widget::input::InstancedWidget::Slider(w) => (w.rounded_corners(), w.corner_radius(), crate::widget::label_offset(w)),
-            };
-            return Some((corners, radius, (vx, vy, vw, vh), label_offset));
-        }
-    }
-    None
-}
-
-fn get_keybinds_control_sub_widget_info(
-    kc: &crate::widget::input::KeybindsControl,
-    qx: f32, qy: f32, qw: f32, qh: f32,
-) -> Option<((bool, bool, bool, bool), f32, (f32, f32, f32, f32), f32)> {
-    // Check add_button
-    let (bx, by, bw, bh) = kc.add_button.rect();
-    if qx >= bx - 0.1 && qx + qw <= bx + bw + 0.1 && qy >= by - 0.1 && qy + qh <= by + bh + 0.1 {
-        return Some((
-            kc.add_button.rounded_corners(),
-            kc.add_button.corner_radius(),
-            (bx, by, bw, bh),
-            crate::widget::label_offset(&kc.add_button),
-        ));
-    }
-    // Check rows
-    for row in &kc.rows {
-        // key_input
-        let (kx, ky, kw, kh) = row.key_input.rect();
-        if qx >= kx - 0.1 && qx + qw <= kx + kw + 0.1 && qy >= ky - 0.1 && qy + qh <= ky + kh + 0.1 {
-            return Some((
-                row.key_input.rounded_corners(),
-                row.key_input.corner_radius(),
-                (kx, ky, kw, kh),
-                crate::widget::label_offset(&row.key_input),
-            ));
-        }
-        // cmd_input
-        let (cx, cy, cw, ch) = row.cmd_input.rect();
-        if qx >= cx - 0.1 && qx + qw <= cx + cw + 0.1 && qy >= cy - 0.1 && qy + qh <= cy + ch + 0.1 {
-            return Some((
-                row.cmd_input.rounded_corners(),
-                row.cmd_input.corner_radius(),
-                (cx, cy, cw, ch),
-                crate::widget::label_offset(&row.cmd_input),
-            ));
-        }
-        // remove_button
-        let (rx, ry, rw, rh) = row.remove_button.rect();
-        if qx >= rx - 0.1 && qx + qw <= rx + rw + 0.1 && qy >= ry - 0.1 && qy + qh <= ry + rh + 0.1 {
-            return Some((
-                row.remove_button.rounded_corners(),
-                row.remove_button.corner_radius(),
-                (rx, ry, rw, rh),
-                crate::widget::label_offset(&row.remove_button),
-            ));
-        }
-    }
-    None
-}
 
 pub fn render_widget<T: Element + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32, ctx: &mut UiContext) {
     let id = w.base().map(|b| b.id());
@@ -3269,35 +3159,6 @@ pub fn render_widget<T: Element + 'static>(pc: &mut dyn RenderTarget, w: &mut T,
     whh -= top_room;
  
     for (qx, qy, qw, qh, qc) in w.all_quads(ctx) {
-        let mut corners = corners;
-        let mut r = r;
-        let mut wx = wx;
-        let mut wy = wy;
-        let mut www = www;
-        let mut whh = whh;
- 
-        if let Some(mc) = w.as_any().downcast_ref::<crate::widget::input::MultiControl>() {
-            if let Some((sub_corners, sub_radius, (sub_x, sub_y, sub_w, sub_h), sub_label_offset)) = get_multicontrol_sub_widget_info(mc, qx, qy, qw, qh) {
-                corners = sub_corners;
-                r = sub_radius;
-                wx = sub_x;
-                wy = sub_y + sub_label_offset;
-                www = sub_w;
-                whh = sub_h - sub_label_offset;
-            }
-        }
-
-        if let Some(kc) = w.as_any().downcast_ref::<crate::widget::input::KeybindsControl>() {
-            if let Some((sub_corners, sub_radius, (sub_x, sub_y, sub_w, sub_h), sub_label_offset)) = get_keybinds_control_sub_widget_info(kc, qx, qy, qw, qh) {
-                corners = sub_corners;
-                r = sub_radius;
-                wx = sub_x;
-                wy = sub_y + sub_label_offset;
-                www = sub_w;
-                whh = sub_h - sub_label_offset;
-            }
-        }
-
         let extra_corners = (
             corners.0 && qx <= wx + 1.5 && qy <= wy + 1.5,
             corners.1 && qx + qw >= wx + www - 1.5 && qy <= wy + 1.5,
@@ -3688,9 +3549,7 @@ impl Section {
         let total_h = wh + top_room;
 
         let name = w.type_name();
-        let span_full = name == "KeybindsControl"
-            || name == "MultiControl"
-            || name == "Trackpad"
+        let span_full = name == "Trackpad"
             || name == "Canvas"
             || name == "UsageBar"
             || name == "ProgressBar"
@@ -4857,9 +4716,7 @@ impl<'a, P: RenderTarget> SectionContext<'a, P> {
         let total_h = wh + top_room;
 
         let name = w.type_name();
-        let span_full = name == "KeybindsControl"
-            || name == "MultiControl"
-            || name == "Trackpad"
+        let span_full = name == "Trackpad"
             || name == "Canvas"
             || name == "UsageBar"
             || name == "ProgressBar"
diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index 43a385b..7af6d25 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -20,7 +20,6 @@ pub mod treelist;
 pub mod control_panel;
 pub mod vbox;
 pub mod hbox;
-pub mod splitbox;
 
 pub use container::Container;
 pub use control_panel::ControlPanel;
@@ -44,4 +43,3 @@ pub use scroll_bar::ScrollBar;
 pub use treelist::{TreeList, TreeElement};
 pub use vbox::VBox;
 pub use hbox::HBox;
-pub use splitbox::{SplitBox, SplitDirection};
diff --git a/src/widget/container/splitbox.rs b/src/widget/container/splitbox.rs
deleted file mode 100644
index 5a6b64d..0000000
--- a/src/widget/container/splitbox.rs
+++ /dev/null
@@ -1,475 +0,0 @@
-use crate::widget::*;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum SplitDirection {
-    Horizontal,
-    Vertical,
-}
-
-pub struct SplitBox {
-    pub base: Widget,
-    pub children: Vec<*mut (dyn Element + 'static)>,
-    pub parent: Option<*mut (dyn Element + 'static)>,
-    pub direction: SplitDirection,
-    pub proportions: Vec<f32>,
-    pub min_sizes: Vec<f32>,
-    pub gap: f32,
-    pub dragging_idx: Option<usize>,
-    pub hovered_idx: Option<usize>,
-}
-
-impl SplitBox {
-    pub fn new(direction: SplitDirection, gap: f32) -> Self {
-        Self {
-            base: Widget::new(),
-            children: Vec::new(),
-            parent: None,
-            direction,
-            proportions: Vec::new(),
-            min_sizes: Vec::new(),
-            gap,
-            dragging_idx: None,
-            hovered_idx: None,
-        }
-    }
-
-    pub fn with_child(mut self, child: *mut (dyn Element + 'static), initial_proportion: f32, min_size: f32) -> Self {
-        self.add_child_with_proportion(child, initial_proportion, min_size);
-        self
-    }
-
-    pub fn add_child_with_proportion(&mut self, child: *mut (dyn Element + 'static), proportion: f32, min_size: f32) {
-        self.children.push(child);
-        self.proportions.push(proportion);
-        self.min_sizes.push(min_size);
-    }
-
-    fn get_splitter_rects(&self) -> Vec<(f32, f32, f32, f32)> {
-        let count = self.children.len();
-        if count <= 1 {
-            return Vec::new();
-        }
-        let (bx, by, bw, bh) = (self.base.x, self.base.y, self.base.w, self.base.h);
-        let total_prop: f32 = self.proportions.iter().sum();
-        if total_prop <= 0.0 {
-            return Vec::new();
-        }
-
-        let mut rects = Vec::new();
-        match self.direction {
-            SplitDirection::Horizontal => {
-                let total_gap = (count - 1) as f32 * self.gap;
-                let child_space = (bw - total_gap).max(0.0);
-                let mut curr_x = bx;
-                for i in 0..(count - 1) {
-                    let w_i = (self.proportions[i] / total_prop) * child_space;
-                    let splitter_x = curr_x + w_i;
-                    rects.push((splitter_x, by, self.gap, bh));
-                    curr_x = splitter_x + self.gap;
-                }
-            }
-            SplitDirection::Vertical => {
-                let total_gap = (count - 1) as f32 * self.gap;
-                let child_space = (bh - total_gap).max(0.0);
-                let mut curr_y = by;
-                for i in 0..(count - 1) {
-                    let h_i = (self.proportions[i] / total_prop) * child_space;
-                    let splitter_y = curr_y + h_i;
-                    rects.push((bx, splitter_y, bw, self.gap));
-                    curr_y = splitter_y + self.gap;
-                }
-            }
-        }
-        rects
-    }
-}
-
-impl Element for SplitBox {
-    crate::impl_widget_base!(SplitBox);
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
-    }
-
-    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        self.children.clone()
-    }
-
-    // --- scene layout-engine opt-in (Phase 2b) ---
-    // A SplitBox is a flex row/column whose child sizing lives on the parent (`proportions`).
-    // These hooks let `scene::bridge::layout_subtree` drive the split via the engine; they are
-    // inert unless an app routes this widget through the bridge (legacy `set_rect` is unchanged).
-    fn layout_style(&self) -> Option<crate::scene::layout::Style> {
-        use crate::scene::layout::{CrossAlign, Style};
-        let mut style = match self.direction {
-            SplitDirection::Horizontal => Style::row(),
-            SplitDirection::Vertical => Style::column(),
-        };
-        style.gap = self.gap;
-        style.cross_align = CrossAlign::Stretch; // panes fill the cross axis, as `set_rect` does
-        Some(style)
-    }
-
-    fn layout_children(&self) -> Option<Vec<crate::scene::layout::Style>> {
-        // Each pane grows by its proportion — matching the proportional split in `set_rect`.
-        Some(self.proportions.iter().map(|&p| crate::scene::layout::Style::default().grow(p)).collect())
-    }
-
-    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;
-        if parent.is_some() {
-            let self_ptr = self as *mut Self;
-            let self_id = self.base.id();
-            unsafe {
-                for &child in &self.children {
-                    let child_id = (*child).base().unwrap().id();
-                    ctx.register_widget(child_id, child);
-                    ctx.link_ids(self_id, child_id);
-                    (*child).set_parent(Some(self_ptr), ctx);
-                }
-            }
-        }
-    }
-
-    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;
-
-        let count = self.children.len();
-        if count == 0 {
-            return;
-        }
-
-        let total_prop: f32 = self.proportions.iter().sum();
-        if total_prop <= 0.0 {
-            return;
-        }
-
-        match self.direction {
-            SplitDirection::Horizontal => {
-                let total_gap = (count - 1) as f32 * self.gap;
-                let child_space = (w - total_gap).max(0.0);
-                let mut curr_x = x;
-                for i in 0..count {
-                    let w_i = (self.proportions[i] / total_prop) * child_space;
-                    unsafe {
-                        (*self.children[i]).set_rect(curr_x, y, w_i, h);
-                    }
-                    curr_x += w_i + self.gap;
-                }
-            }
-            SplitDirection::Vertical => {
-                let total_gap = (count - 1) as f32 * self.gap;
-                let child_space = (h - total_gap).max(0.0);
-                let mut curr_y = y;
-                for i in 0..count {
-                    let h_i = (self.proportions[i] / total_prop) * child_space;
-                    unsafe {
-                        (*self.children[i]).set_rect(x, curr_y, w, h_i);
-                    }
-                    curr_y += h_i + self.gap;
-                }
-            }
-        }
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        let mut handled = false;
-        
-        // Check dragging
-        if let Some(idx) = self.dragging_idx {
-            let total_prop: f32 = self.proportions.iter().sum();
-            if total_prop > 0.0 {
-                let splitters = self.get_splitter_rects();
-                if idx < splitters.len() {
-                    match self.direction {
-                        SplitDirection::Horizontal => {
-                            
-                            // Find combine range of idx and idx+1
-                            let left_child_x = if idx == 0 { self.base.x } else {
-                                unsafe {
-                                    let r = (*self.children[idx]).rect();
-                                    r.0
-                                }
-                            };
-                            let right_child_end = unsafe {
-                                let r = (*self.children[idx + 1]).rect();
-                                r.0 + r.2
-                            };
-                            
-                            let combined_w = (right_child_end - left_child_x - self.gap).max(0.0);
-                            let min_left = self.min_sizes.get(idx).cloned().unwrap_or(20.0);
-                            let min_right = self.min_sizes.get(idx + 1).cloned().unwrap_or(20.0);
-                            
-                            let new_left_w = (px - left_child_x - self.gap / 2.0).clamp(min_left, combined_w - min_right);
-                            
-                            let comb_prop = self.proportions[idx] + self.proportions[idx + 1];
-                            if combined_w > 0.1 {
-                                self.proportions[idx] = (new_left_w / combined_w) * comb_prop;
-                                self.proportions[idx + 1] = comb_prop - self.proportions[idx];
-                                let (bx, by, bw, bh) = (self.base.x, self.base.y, self.base.w, self.base.h);
-                                self.set_rect(bx, by, bw, bh);
-                                self.mark_dirty(ctx);
-                                handled = true;
-                            }
-                        }
-                        SplitDirection::Vertical => {
-                            
-                            // Find combine range of idx and idx+1
-                            let top_child_y = if idx == 0 { self.base.y } else {
-                                unsafe {
-                                    let r = (*self.children[idx]).rect();
-                                    r.1
-                                }
-                            };
-                            let bottom_child_end = unsafe {
-                                let r = (*self.children[idx + 1]).rect();
-                                r.1 + r.3
-                            };
-                            
-                            let combined_h = (bottom_child_end - top_child_y - self.gap).max(0.0);
-                            let min_top = self.min_sizes.get(idx).cloned().unwrap_or(20.0);
-                            let min_bottom = self.min_sizes.get(idx + 1).cloned().unwrap_or(20.0);
-                            
-                            let new_top_h = (py - top_child_y - self.gap / 2.0).clamp(min_top, combined_h - min_bottom);
-                            
-                            let comb_prop = self.proportions[idx] + self.proportions[idx + 1];
-                            if combined_h > 0.1 {
-                                self.proportions[idx] = (new_top_h / combined_h) * comb_prop;
-                                self.proportions[idx + 1] = comb_prop - self.proportions[idx];
-                                let (bx, by, bw, bh) = (self.base.x, self.base.y, self.base.w, self.base.h);
-                                self.set_rect(bx, by, bw, bh);
-                                self.mark_dirty(ctx);
-                                handled = true;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Hover checking
-        let mut new_hovered = None;
-        if self.dragging_idx.is_none() {
-            let splitters = self.get_splitter_rects();
-            for (i, &(sx, sy, sw, sh)) in splitters.iter().enumerate() {
-                if px >= sx && px <= sx + sw && py >= sy && py <= sy + sh {
-                    new_hovered = Some(i);
-                    break;
-                }
-            }
-        }
-        if self.hovered_idx != new_hovered {
-            self.hovered_idx = new_hovered;
-            self.mark_dirty(ctx);
-            handled = true;
-        }
-
-        handled
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if button == MouseButton::Left {
-            match state {
-                ElementState::Pressed => {
-                    let splitters = self.get_splitter_rects();
-                    for (i, &(sx, sy, sw, sh)) in splitters.iter().enumerate() {
-                        if px >= sx && px <= sx + sw && py >= sy && py <= sy + sh {
-                            self.dragging_idx = Some(i);
-                            ctx.set_focused_ptr(self.as_ptr_mut());
-                            self.mark_dirty(ctx);
-                            return true;
-                        }
-                    }
-                }
-                ElementState::Released => {
-                    if self.dragging_idx.is_some() {
-                        self.dragging_idx = None;
-                        ctx.clear_focus();
-                        self.mark_dirty(ctx);
-                        return true;
-                    }
-                }
-            }
-        }
-
-        false
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        let splitters = self.get_splitter_rects();
-        for (i, &(sx, sy, sw, sh)) in splitters.iter().enumerate() {
-            let is_dragging = self.dragging_idx == Some(i);
-            let is_hovered = self.hovered_idx == Some(i);
-            if is_dragging {
-                quads.push((sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.36, 0.56, 0.38, 0.8]));
-            } else if is_hovered {
-                quads.push((sx + sw / 2.0 - 1.0, sy, 2.0, sh, [0.25, 0.25, 0.32, 0.6]));
-            } else {
-                quads.push((sx + sw / 2.0 - 0.5, sy, 1.0, sh, [0.15, 0.15, 0.18, 0.4]));
-            }
-        }
-        quads
-    }
-
-    // SplitBox is a real layout container: aggregate each child's quads and text
-    // (clipped to our bounds), so a self-rendering widget placed inside the split
-    // is drawn through the widget tree rather than needing to be rendered manually.
-    // Mirrors Backplate's aggregation. A child's own background is emitted through
-    // exactly one path (rounded → all_rounded_quads, otherwise extra_quads), so the
-    // has_rounded guard below prevents a square underlay from doubling a rounded bg.
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible() {
-            return Vec::new();
-        }
-        let mut quads = self.extra_quads();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            let (cx, cy, cw, ch) = widget.rect();
-            let child_has_rounded = widget.rounded_corners() != (false, false, false, false);
-            for (qx, qy, qw, qh, qc) in widget.all_quads(ctx) {
-                if child_has_rounded
-                    && (qx - cx).abs() < 0.1 && (qy - cy).abs() < 0.1
-                    && (qw - cw).abs() < 0.1 && (qh - ch).abs() < 0.1
-                {
-                    continue;
-                }
-                let x0 = qx.max(wx);
-                let y0 = qy.max(wy);
-                let x1 = (qx + qw).min(wx + ww);
-                let y1 = (qy + qh).min(wy + wh);
-                if x1 > x0 && y1 > y0 {
-                    quads.push((x0, y0, x1 - x0, y1 - y0, qc));
-                }
-            }
-        }
-        quads
-    }
-
-    fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
-        if !self.visible() {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            for (qx, qy, qw, qh, qr, qc, corners) in widget.all_rounded_quads(ctx) {
-                let x0 = qx.max(wx);
-                let y0 = qy.max(wy);
-                let x1 = (qx + qw).min(wx + ww);
-                let y1 = (qy + qh).min(wy + wh);
-                if x1 > x0 && y1 > y0 {
-                    quads.push((x0, y0, x1 - x0, y1 - y0, qr, qc, corners));
-                }
-            }
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            labels.extend(widget.text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let mut result = Vec::new();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            for (label, bounds) in widget.text_labels_with_bounds(ctx) {
-                let cb = match bounds {
-                    Some(b) => {
-                        let cx0 = b[0].max(wx);
-                        let cy0 = b[1].max(wy);
-                        let cx1 = b[2].min(wx + ww);
-                        let cy1 = b[3].min(wy + wh);
-                        if cx1 > cx0 && cy1 > cy0 {
-                            Some([cx0, cy0, cx1, cy1])
-                        } else {
-                            continue;
-                        }
-                    }
-                    None => Some([wx, wy, wx + ww, wy + wh]),
-                };
-                result.push((label, cb));
-            }
-        }
-        result
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut result = Vec::new();
-        let (wx, wy, ww, wh) = self.rect();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            for (label, font, bounds) in widget.text_labels_with_font_and_bounds(ctx) {
-                let cb = match bounds {
-                    Some(b) => {
-                        let cx0 = b[0].max(wx);
-                        let cy0 = b[1].max(wy);
-                        let cx1 = b[2].min(wx + ww);
-                        let cy1 = b[3].min(wy + wh);
-                        if cx1 > cx0 && cy1 > cy0 {
-                            Some([cx0, cy0, cx1, cy1])
-                        } else {
-                            continue;
-                        }
-                    }
-                    None => Some([wx, wy, wx + ww, wy + wh]),
-                };
-                result.push((label, font, cb));
-            }
-        }
-        result
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut result = Vec::new();
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.get_text_items());
-        }
-        result
-    }
-
-    fn set_hovered(&mut self, v: bool) {
-        if !v {
-            self.hovered_idx = None;
-        }
-        for &child in &self.children {
-            unsafe {
-                (*child).set_hovered(v);
-            }
-        }
-    }
-
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), _ctx: &mut UiContext) {
-        self.children.push(child);
-        self.proportions.push(1.0);
-        self.min_sizes.push(20.0);
-    }
-
-    fn clear_children(&mut self, _ctx: &mut UiContext) {
-        self.children.clear();
-        self.proportions.clear();
-        self.min_sizes.clear();
-        self.dragging_idx = None;
-        self.hovered_idx = None;
-    }
-}
-
-unsafe impl Send for SplitBox {}
-unsafe impl Sync for SplitBox {}
diff --git a/src/widget/input/keybinds_control.rs b/src/widget/input/keybinds_control.rs
deleted file mode 100644
index 79bae98..0000000
--- a/src/widget/input/keybinds_control.rs
+++ /dev/null
@@ -1,394 +0,0 @@
-use crate::colors;
-use crate::widget::*;
-use crate::widget::input::text_box::TextBox;
-use crate::widget::input::button::Button;
-use crate::widget::TextLabel;
-
-#[derive(Clone, Debug)]
-pub struct KeybindRow {
-    pub key_input: crate::widget::Adapted<TextBox>,
-    pub cmd_input: crate::widget::Adapted<TextBox>,
-    pub remove_button: Adapted<Button>,
-}
-
-impl KeybindRow {
-    pub fn new(binding: String, command: String) -> Self {
-        let key_input = TextBox::new(binding).with_placeholder("e.g. super+shift+q");
-        let cmd_input = TextBox::new(command).with_placeholder("e.g. close");
-        
-        let remove_icon = format!("{}/x.svg", crate::icons_dir());
-        let remove_svg = Svg::from_file(&remove_icon, 0.0, 0.0, 14.0, 14.0);
-        let mut remove_button = Button::new(0.0, 0.0, 0.0, 0.0);
-        if let Some(svg) = remove_svg {
-            remove_button = remove_button.with_svg(svg);
-        } else {
-            remove_button = remove_button.with_label("Remove");
-        }
-        
-        Self {
-            key_input,
-            cmd_input,
-            remove_button,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct KeybindsControl {
-    pub base: Widget,
-    pub rows: Vec<KeybindRow>,
-    pub add_button: Adapted<Button>,
-    pub just_changed: bool,
-}
-
-impl KeybindsControl {
-    pub fn new() -> Self {
-        let mut kc = Self {
-            base: Widget::new(),
-            rows: Vec::new(),
-            add_button: Button::new(0.0, 0.0, 0.0, 0.0).with_label("Add Keybind +"),
-            just_changed: false,
-        };
-        kc.load_from_config();
-        kc
-    }
-
-    pub fn load_from_config(&mut self) {
-        let path = crate::config::get_config_path();
-        let content = std::fs::read_to_string(&path).unwrap_or_default();
-        let val = crate::config::parse_kdl_to_json(&content);
-        let mut loaded = Vec::new();
-        let mut keybind_val = val.get("key_bindings");
-        if keybind_val.is_none() {
-            keybind_val = val.get("input").and_then(|i| i.get("key_bindings"));
-        }
-        if let Some(arr) = keybind_val.and_then(|k| k.as_array()) {
-            for v in arr {
-                let mods = v.get("mods").and_then(|m| m.as_str()).unwrap_or("").to_string();
-                let key = v.get("key").and_then(|k| k.as_str()).unwrap_or("").to_string();
-                let action = v.get("action").and_then(|a| a.as_str()).unwrap_or("").to_string();
-                let command = v.get("command").and_then(|c| c.as_str()).unwrap_or("").to_string();
-                
-                let binding = if mods.is_empty() {
-                    key
-                } else {
-                    format!("{}+{}", mods, key)
-                };
-                
-                let mut cmd_val = if action == "spawn" {
-                    command.clone()
-                } else if action == "toggle" {
-                    command.clone()
-                } else {
-                    action.clone()
-                };
-
-                if cmd_val.starts_with("ccectl ") {
-                    let sub = &cmd_val["ccectl ".len()..];
-                    if sub.starts_with("view ") {
-                        let num = &sub["view ".len()..];
-                        cmd_val = format!("view-{}", num);
-                    } else if sub.starts_with("set-tag ") {
-                        let num = &sub["set-tag ".len()..];
-                        cmd_val = format!("set-tag-{}", num);
-                    } else {
-                        cmd_val = sub.to_string();
-                    }
-                }
-                
-                loaded.push((binding, cmd_val));
-            }
-        }
-        self.rows = loaded.into_iter().map(|(binding, cmd_val)| {
-            KeybindRow::new(binding, cmd_val)
-        }).collect();
-    }
-
-    pub fn save_to_config(&self) {
-        let path = crate::config::get_config_path();
-        if let Some(parent) = path.parent() {
-            let _ = std::fs::create_dir_all(parent);
-        }
-        
-        let mut keybinds = Vec::new();
-        for row in &self.rows {
-            let binding = row.key_input.get_value_string().unwrap_or_default();
-            let cmd_val = row.cmd_input.get_value_string().unwrap_or_default();
-            
-            let (mods, key) = if let Some(last_plus) = binding.rfind('+') {
-                (binding[..last_plus].to_string(), binding[last_plus + 1..].to_string())
-            } else {
-                ("".to_string(), binding)
-            };
-
-            let built_in_actions = [
-                "expose", "close", "minimize", "focus-next", "focus-prev", "mode-next",
-                "mode-next-shared", "fullscreen", "exit", "reload",
-                "zoom-in", "zoom-out", "zoom-reset", "pan-left", "pan-right", "pan-up", "pan-down",
-                "overlay-left", "overlay-right"
-            ];
-            
-            let mut obj = serde_json::Map::new();
-            obj.insert("mods".to_string(), serde_json::Value::String(mods));
-            obj.insert("key".to_string(), serde_json::Value::String(key));
-            
-            if built_in_actions.contains(&cmd_val.as_str()) {
-                obj.insert("action".to_string(), serde_json::Value::String("spawn".to_string()));
-                obj.insert("command".to_string(), serde_json::Value::String(format!("ccectl {}", cmd_val)));
-            } else if cmd_val.starts_with("view-") {
-                let num = &cmd_val["view-".len()..];
-                obj.insert("action".to_string(), serde_json::Value::String("spawn".to_string()));
-                obj.insert("command".to_string(), serde_json::Value::String(format!("ccectl view {}", num)));
-            } else if cmd_val.starts_with("set-tag-") {
-                let num = &cmd_val["set-tag-".len()..];
-                obj.insert("action".to_string(), serde_json::Value::String("spawn".to_string()));
-                obj.insert("command".to_string(), serde_json::Value::String(format!("ccectl set-tag {}", num)));
-            } else {
-                obj.insert("action".to_string(), serde_json::Value::String("spawn".to_string()));
-                obj.insert("command".to_string(), serde_json::Value::String(cmd_val));
-            }
-            
-            keybinds.push(serde_json::Value::Object(obj));
-        }
-        
-        crate::config::write_keybindings_to_kdl(path.to_str().unwrap(), &keybinds);
-    }
-}
-
-fn link_child(parent_ptr: *mut (dyn Element + 'static), parent_id: WidgetId, child: &mut dyn Element, ctx: &mut UiContext) {
-    let c_ptr = child.as_ptr();
-    if let Some(c_base) = child.base() {
-        let c_id = c_base.id();
-        ctx.register_widget(parent_id, parent_ptr);
-        ctx.register_widget(c_id, c_ptr);
-        ctx.link_ids(parent_id, c_id);
-    }
-    child.set_parent(Some(parent_ptr), ctx);
-}
-
-impl Element for KeybindsControl {
-    crate::impl_widget_base!(KeybindsControl);
-
-    fn preferred_height(&self) -> Option<f32> {
-        let pad_y = 8.0;
-        let gap_between_rows = 8.0;
-        let row_h = crate::layout::textbox_height();
-        let add_btn_h = 30.0;
-
-        let mut total_h = pad_y * 2.0;
-        if !self.rows.is_empty() {
-            total_h += self.rows.len() as f32 * row_h + (self.rows.len() - 1) as f32 * gap_between_rows;
-            total_h += gap_between_rows;
-        }
-        total_h += add_btn_h;
-        Some(total_h)
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
-    }
-
-    fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
-        let size = self.measure(constraints, ctx);
-        self.set_rect(origin.x, origin.y, size.width, size.height);
-
-        let pad_x = 8.0;
-        let pad_y = 8.0;
-        let gap_between_rows = 8.0;
-
-        let row_h = crate::layout::textbox_height();
-
-        let usable_w = (size.width - 2.0 * pad_x).max(1.0);
-        let gap_x = 6.0;
-        let row_usable_w = usable_w - 2.0 * gap_x;
-
-        let key_w = row_usable_w * 0.38;
-        let cmd_w = row_usable_w * 0.50;
-        let remove_w = row_usable_w * 0.12;
-
-        let mut curr_y = origin.y + pad_y;
-        let self_ptr = self.as_ptr();
-        let self_id = self.base.id();
-
-        for row in &mut self.rows {
-            let key_x = origin.x + pad_x;
-            row.key_input.layout(Point { x: key_x, y: curr_y }, LayoutConstraints::new(key_w, key_w, row_h, row_h), ctx);
-
-            let cmd_x = key_x + key_w + gap_x;
-            row.cmd_input.layout(Point { x: cmd_x, y: curr_y }, LayoutConstraints::new(cmd_w, cmd_w, row_h, row_h), ctx);
-
-            let remove_x = cmd_x + cmd_w + gap_x;
-            row.remove_button.layout(Point { x: remove_x, y: curr_y }, LayoutConstraints::new(remove_w, remove_w, row_h, row_h), ctx);
-
-            link_child(self_ptr, self_id, &mut row.key_input, ctx);
-            link_child(self_ptr, self_id, &mut row.cmd_input, ctx);
-            link_child(self_ptr, self_id, &mut row.remove_button, ctx);
-
-            curr_y += row_h + gap_between_rows;
-        }
-
-        let add_btn_w = 120.0f32.min(usable_w);
-        let add_btn_h = 30.0;
-        let add_x = origin.x + pad_x;
-        self.add_button.layout(Point { x: add_x, y: curr_y }, LayoutConstraints::new(add_btn_w, add_btn_w, add_btn_h, add_btn_h), ctx);
-        link_child(self_ptr, self_id, &mut self.add_button, ctx);
-    }
-
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = self.extra_quads();
-        if let Some(hq) = self.highlight_quad(ctx) {
-            if hq.4 != colors::HIGHLIGHT_SECONDARY {
-                quads.push(hq);
-            }
-        }
-
-        for row in &self.rows {
-            quads.extend(row.key_input.all_quads(ctx));
-            quads.extend(row.cmd_input.all_quads(ctx));
-            quads.extend(row.remove_button.all_quads(ctx));
-        }
-
-        quads.extend(self.add_button.all_quads(ctx));
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        Vec::new()
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        for row in &self.rows {
-            labels.extend(row.key_input.text_labels_with_bounds(ctx));
-            labels.extend(row.cmd_input.text_labels_with_bounds(ctx));
-            labels.extend(row.remove_button.text_labels_with_bounds(ctx));
-        }
-        labels.extend(self.add_button.text_labels_with_bounds(ctx));
-        labels
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        let font = self.widget_font();
-        for l in self.text_labels() {
-            labels.push((l, font.clone(), None));
-        }
-
-        for row in &self.rows {
-            labels.extend(row.key_input.text_labels_with_font_and_bounds(ctx));
-            labels.extend(row.cmd_input.text_labels_with_font_and_bounds(ctx));
-            labels.extend(row.remove_button.text_labels_with_font_and_bounds(ctx));
-        }
-
-        labels.extend(self.add_button.text_labels_with_font_and_bounds(ctx));
-        labels
-    }
-
-    fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
-        let mut handled = false;
-
-        if self.add_button.handle_event(event, ctx) {
-            handled = true;
-        }
-
-        let mut row_to_remove = None;
-        for (i, row) in self.rows.iter_mut().enumerate() {
-            if row.key_input.handle_event(event, ctx) {
-                handled = true;
-            }
-            if row.cmd_input.handle_event(event, ctx) {
-                handled = true;
-            }
-            if row.remove_button.handle_event(event, ctx) {
-                handled = true;
-            }
-            if row.remove_button.take_click() {
-                row_to_remove = Some(i);
-                handled = true;
-            }
-        }
-
-        if let Some(idx) = row_to_remove {
-            self.rows.remove(idx);
-            self.save_to_config();
-            self.just_changed = true;
-            handled = true;
-        }
-
-        if self.add_button.take_click() {
-            let new_row = KeybindRow::new("".to_string(), "".to_string());
-            self.rows.push(new_row);
-            self.save_to_config();
-            self.just_changed = true;
-            handled = true;
-        }
-
-        let mut key_changed = false;
-        let mut cmd_changed = false;
-
-        for row in &mut self.rows {
-            if row.key_input.take_change() {
-                key_changed = true;
-            }
-            if row.cmd_input.take_change() {
-                cmd_changed = true;
-            }
-        }
-
-        if key_changed || cmd_changed {
-            self.save_to_config();
-            self.just_changed = true;
-        }
-
-        if !handled {
-            match event {
-                Event::PointerMove { x, y, .. } => {
-                    let is_hit = self.hit_test(*x, *y, ctx);
-                    let was = self.hovered();
-                    self.set_hovered(is_hit);
-                    if was != is_hit {
-                        handled = true;
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        handled
-    }
-
-    fn take_change(&mut self) -> bool {
-        let ret = self.just_changed;
-        self.just_changed = false;
-        ret
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::MouseButton { button, state, x: px, y: py, local_x: px, local_y: py }, ctx)
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::KeyInput(event.clone()), ctx)
-    }
-
-    fn cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::PointerMove { x: px, y: py, local_x: px, local_y: py }, ctx)
-    }
-
-    fn unfocus(&mut self) {
-        for row in &mut self.rows {
-            row.key_input.unfocus();
-            row.cmd_input.unfocus();
-        }
-    }
-
-    fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
-        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
-    }
-}
-
-impl Control for KeybindsControl {}
diff --git a/src/widget/input/mod.rs b/src/widget/input/mod.rs
index 804f23a..9e6f665 100644
--- a/src/widget/input/mod.rs
+++ b/src/widget/input/mod.rs
@@ -9,8 +9,6 @@ pub mod text_box;
 pub mod trackpad;
 pub mod font_selector;
 pub mod button_strip;
-pub mod multi_control;
-pub mod keybinds_control;
 pub mod keybind_recorder;
 pub mod ramp;
 
@@ -25,8 +23,6 @@ pub use text_box::{TextBox, get_font_db};
 pub use trackpad::{Trackpad, Finger};
 pub use font_selector::FontSelector;
 pub use button_strip::ButtonStrip;
-pub use multi_control::{MultiControl, InstancedControl, InstancedWidget, MultiControlRow};
-pub use keybinds_control::{KeybindsControl, KeybindRow};
 pub use keybind_recorder::KeybindRecorder;
 pub use ramp::{Ramp, RampKey, ColorRamp, ColorRampKey};
 
diff --git a/src/widget/input/multi_control.rs b/src/widget/input/multi_control.rs
deleted file mode 100644
index 66c339b..0000000
--- a/src/widget/input/multi_control.rs
+++ /dev/null
@@ -1,1193 +0,0 @@
-use crate::colors;
-use crate::widget::*;
-use crate::widget::input::{TextBox, Spinbox, Dropdown, Button, Toggle, Slider};
-use crate::widget::TextLabel;
-
-#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
-pub struct InstancedControl {
-    pub key: String,
-    pub control_type: String,
-    pub value: String,
-}
-
-#[derive(Clone, Debug)]
-pub enum InstancedWidget {
-    TextBox(Adapted<TextBox>),
-    Spinbox(Adapted<Spinbox>),
-    Toggle(Adapted<Toggle>),
-    Slider(Adapted<Slider>),
-}
-
-impl InstancedWidget {
-    pub fn get_value_string(&self) -> Option<String> {
-        match self {
-            InstancedWidget::TextBox(w) => w.get_value_string(),
-            InstancedWidget::Spinbox(w) => w.get_value_string(),
-            InstancedWidget::Toggle(w) => w.get_value_string(),
-            InstancedWidget::Slider(w) => w.get_value_string(),
-        }
-    }
-
-    pub fn set_value_string(&mut self, val: &str) -> bool {
-        match self {
-            InstancedWidget::TextBox(w) => w.set_value_string(val),
-            InstancedWidget::Spinbox(w) => w.set_value_string(val),
-            InstancedWidget::Toggle(w) => w.set_value_string(val),
-            InstancedWidget::Slider(w) => w.set_value_string(val),
-        }
-    }
-
-    pub fn take_change(&mut self) -> bool {
-        match self {
-            InstancedWidget::TextBox(w) => w.take_change(),
-            InstancedWidget::Spinbox(w) => w.take_change(),
-            InstancedWidget::Toggle(w) => w.take_change(),
-            InstancedWidget::Slider(w) => w.take_change(),
-        }
-    }
-
-    pub fn preferred_height(&self) -> Option<f32> {
-        match self {
-            InstancedWidget::TextBox(w) => w.preferred_height(),
-            InstancedWidget::Spinbox(w) => w.preferred_height(),
-            InstancedWidget::Toggle(w) => w.preferred_height(),
-            InstancedWidget::Slider(w) => w.preferred_height(),
-        }
-    }
-
-    pub fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
-        match self {
-            InstancedWidget::TextBox(w) => w.layout(origin, constraints, ctx),
-            InstancedWidget::Spinbox(w) => w.layout(origin, constraints, ctx),
-            InstancedWidget::Toggle(w) => w.layout(origin, constraints, ctx),
-            InstancedWidget::Slider(w) => w.layout(origin, constraints, ctx),
-        }
-    }
-
-    pub fn rect(&self) -> (f32, f32, f32, f32) {
-        match self {
-            InstancedWidget::TextBox(w) => w.rect(),
-            InstancedWidget::Spinbox(w) => w.rect(),
-            InstancedWidget::Toggle(w) => w.rect(),
-            InstancedWidget::Slider(w) => w.rect(),
-        }
-    }
-
-    pub fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        match self {
-            InstancedWidget::TextBox(w) => w.all_quads(ctx),
-            InstancedWidget::Spinbox(w) => w.all_quads(ctx),
-            InstancedWidget::Toggle(w) => w.all_quads(ctx),
-            InstancedWidget::Slider(w) => w.all_quads(ctx),
-        }
-    }
-
-    pub fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        match self {
-            InstancedWidget::TextBox(w) => w.text_labels_with_bounds(ctx),
-            InstancedWidget::Spinbox(w) => w.text_labels_with_bounds(ctx),
-            InstancedWidget::Toggle(w) => w.text_labels_with_bounds(ctx),
-            InstancedWidget::Slider(w) => w.text_labels_with_bounds(ctx),
-        }
-    }
-
-    pub fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        match self {
-            InstancedWidget::TextBox(w) => w.text_labels_with_font_and_bounds(ctx),
-            InstancedWidget::Spinbox(w) => w.text_labels_with_font_and_bounds(ctx),
-            InstancedWidget::Toggle(w) => w.text_labels_with_font_and_bounds(ctx),
-            InstancedWidget::Slider(w) => w.text_labels_with_font_and_bounds(ctx),
-        }
-    }
-
-    pub fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
-        match self {
-            InstancedWidget::TextBox(w) => w.handle_event(event, ctx),
-            InstancedWidget::Spinbox(w) => w.handle_event(event, ctx),
-            InstancedWidget::Toggle(w) => w.handle_event(event, ctx),
-            InstancedWidget::Slider(w) => w.handle_event(event, ctx),
-        }
-    }
-
-    pub fn unfocus(&mut self) {
-        match self {
-            InstancedWidget::TextBox(w) => w.unfocus(),
-            InstancedWidget::Spinbox(w) => w.unfocus(),
-            InstancedWidget::Toggle(w) => w.unfocus(),
-            InstancedWidget::Slider(w) => w.unfocus(),
-        }
-    }
-
-    pub fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
-        match self {
-            InstancedWidget::TextBox(w) => w.hit_test(px, py, ctx),
-            InstancedWidget::Spinbox(w) => w.hit_test(px, py, ctx),
-            InstancedWidget::Toggle(w) => w.hit_test(px, py, ctx),
-            InstancedWidget::Slider(w) => w.hit_test(px, py, ctx),
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct MultiControlRow {
-    pub key_input: Adapted<TextBox>,
-    pub type_dropdown: Adapted<Dropdown>,
-    pub value_widget: InstancedWidget,
-    pub remove_button: Adapted<Button>,
-    pub layout_y: f32,
-    pub layout_height: f32,
-    pub natural_y: f32,
-}
-
-impl MultiControlRow {
-    pub fn new(key: String, control_type: String, value: String) -> Self {
-        let key_input = TextBox::new(key);
-        let dropdown_options = vec![
-            "TextBox".to_string(),
-            "Spinbox".to_string(),
-            "Toggle".to_string(),
-            "Slider".to_string(),
-        ];
-        let type_idx = match control_type.as_str() {
-            "Spinbox" => 1,
-            "Toggle" => 2,
-            "Slider" => 3,
-            _ => 0,
-        };
-        let type_dropdown = Dropdown::new(dropdown_options, type_idx);
-
-        let value_widget = match type_idx {
-            1 => {
-                let initial_val = value.parse::<i32>().unwrap_or(0);
-                InstancedWidget::Spinbox(Spinbox::new(initial_val, 0, 1000000, 1))
-            }
-            2 => {
-                let initial_val = value.trim().to_lowercase();
-                let toggled = initial_val == "true" || initial_val == "1" || initial_val == "yes" || initial_val == "on";
-                let mut tg = Toggle::new();
-                tg.set_toggled(toggled);
-                InstancedWidget::Toggle(tg)
-            }
-            3 => {
-                let mut sl = Slider::new();
-                sl.set_value_string(&value);
-                InstancedWidget::Slider(sl)
-            }
-            _ => {
-                InstancedWidget::TextBox(TextBox::new(value))
-            }
-        };
-
-        let remove_button = Button::new(0.0, 0.0, 0.0, 0.0).with_label("Remove");
-
-        Self {
-            key_input,
-            type_dropdown,
-            value_widget,
-            remove_button,
-            layout_y: 0.0,
-            layout_height: 0.0,
-            natural_y: 0.0,
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct MultiControl {
-    pub base: Widget,
-    pub name: String,
-    pub rows: Vec<MultiControlRow>,
-    pub add_button: Adapted<Button>,
-    pub just_changed: bool,
-    pub add_popover_open: bool,
-    pub add_popover_hovered_idx: Option<usize>,
-    pub active_drag_index: Option<usize>,
-    pub drag_start_mouse_y: f32,
-    pub drag_y_offset: f32,
-    pub hovered_drag_index: Option<usize>,
-}
-
-impl MultiControl {
-    pub fn new(name: String) -> Self {
-        let mut mc = Self {
-            base: Widget::new(),
-            name,
-            rows: Vec::new(),
-            add_button: Button::new(0.0, 0.0, 0.0, 0.0).with_label("Add Widget +"),
-            just_changed: false,
-            add_popover_open: false,
-            add_popover_hovered_idx: None,
-            active_drag_index: None,
-            drag_start_mouse_y: 0.0,
-            drag_y_offset: 0.0,
-            hovered_drag_index: None,
-        };
-        mc.load_from_config();
-        mc
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self.load_from_config();
-        self
-    }
-
-    pub fn load_from_config(&mut self) {
-        let name = if self.base.label.is_some() {
-            self.base.label.as_ref().unwrap()
-        } else {
-            &self.name
-        };
-        let loaded = load_config(name);
-        self.rows = loaded.into_iter().map(|c| {
-            MultiControlRow::new(c.key, c.control_type, c.value)
-        }).collect();
-    }
-
-    pub fn save_to_config(&self) {
-        let name = if self.base.label.is_some() {
-            self.base.label.as_ref().unwrap()
-        } else {
-            &self.name
-        };
-        let controls: Vec<InstancedControl> = self.rows.iter().map(|row| {
-            let key = row.key_input.get_value_string().unwrap_or_default();
-            let control_type = match row.type_dropdown.selected {
-                1 => "Spinbox".to_string(),
-                2 => "Toggle".to_string(),
-                3 => "Slider".to_string(),
-                _ => "TextBox".to_string(),
-            };
-            let value = row.value_widget.get_value_string().unwrap_or_default();
-            InstancedControl { key, control_type, value }
-        }).collect();
-        save_config(name, &controls);
-    }
-
-    pub fn get_drag_handle_quads(&self, idx: usize, theme: colors::Theme) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if idx >= self.rows.len() {
-            return quads;
-        }
-        let row = &self.rows[idx];
-        let (rx, _, _, _) = self.rect();
-        let pad_x = 8.0;
-        let drag_handle_w = 20.0;
-        
-        let key_h = crate::layout::textbox_height();
-        let type_h = crate::layout::dropdown_height();
-        let line1_h = key_h.max(type_h);
-        
-        let grip_center_y = row.layout_y + line1_h * 0.5;
-        let grip_center_x = rx + pad_x + drag_handle_w * 0.5;
-        
-        // Let's decide color based on hover / drag state
-        let color = if Some(idx) == self.active_drag_index {
-            theme.primary_accent
-        } else if Some(idx) == self.hovered_drag_index {
-            [
-                (theme.primary_accent[0] + 0.1).min(1.0),
-                (theme.primary_accent[1] + 0.1).min(1.0),
-                (theme.primary_accent[2] + 0.1).min(1.0),
-                0.8
-            ]
-        } else {
-            [
-                theme.surface_border[0],
-                theme.surface_border[1],
-                theme.surface_border[2],
-                0.5
-            ]
-        };
-
-        // Let's draw 3 horizontal bars for the grip
-        let bar_w = 10.0;
-        let bar_h = 2.0;
-        let bar_gap = 2.0;
-        let total_grip_h = 3.0 * bar_h + 2.0 * bar_gap;
-        let start_y = grip_center_y - total_grip_h * 0.5;
-        let start_x = grip_center_x - bar_w * 0.5;
-
-        for i in 0..3 {
-            let y = start_y + i as f32 * (bar_h + bar_gap);
-            quads.push((start_x, y, bar_w, bar_h, color));
-        }
-
-        quads
-    }
-}
-
-fn link_child(parent_ptr: *mut (dyn Element + 'static), parent_id: WidgetId, child: &mut dyn Element, ctx: &mut UiContext) {
-    let c_ptr = child.as_ptr();
-    if let Some(c_base) = child.base() {
-        let c_id = c_base.id();
-        ctx.register_widget(parent_id, parent_ptr);
-        ctx.register_widget(c_id, c_ptr);
-        ctx.link_ids(parent_id, c_id);
-    }
-    child.set_parent(Some(parent_ptr), ctx);
-}
-
-impl Element for MultiControl {
-    crate::impl_widget_base!(MultiControl);
-
-    fn preferred_height(&self) -> Option<f32> {
-        let gap_between_lines = 4.0;
-        let gap_between_rows = 12.0;
-        let add_btn_h = 36.0;
-
-        let key_h = crate::layout::textbox_height();
-        let type_h = crate::layout::dropdown_height();
-        let line1_h = key_h.max(type_h);
-
-        let mut total_h = 0.0;
-        if self.rows.is_empty() {
-            total_h = add_btn_h + 16.0;
-        } else {
-            for row in &self.rows {
-                let line2_h = row.value_widget.preferred_height().unwrap_or(44.0);
-                total_h += line1_h + gap_between_lines + line2_h;
-            }
-            total_h += (self.rows.len() - 1) as f32 * gap_between_rows + add_btn_h + 28.0;
-        }
-        Some(total_h)
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
-    }
-
-    fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
-        let size = self.measure(constraints, ctx);
-        self.set_rect(origin.x, origin.y, size.width, size.height);
-
-        let pad_x = 8.0;
-        let pad_y = 8.0;
-        let gap_between_lines = 4.0;
-        let gap_between_rows = 12.0;
-
-        let key_h = crate::layout::textbox_height();
-        let type_h = crate::layout::dropdown_height();
-        let line1_h = key_h.max(type_h);
-
-        let drag_handle_w = 20.0;
-        let drag_gap_x = 6.0;
-        let left_shift = drag_handle_w + drag_gap_x;
-
-        let usable_w = (size.width - 2.0 * pad_x - left_shift).max(1.0);
-        let gap_x = 6.0;
-        let top_usable_w = usable_w - 2.0 * gap_x;
-
-        let key_w = top_usable_w * 0.50;
-        let type_w = top_usable_w * 0.32;
-        let remove_w = top_usable_w * 0.18;
-
-        let mut curr_y = origin.y + pad_y;
-        let self_ptr = self.as_ptr();
-        let self_id = self.base.id();
-
-        // Calculate rows area height for clamping
-        let mut total_rows_h = 0.0;
-        for (i, row) in self.rows.iter().enumerate() {
-            let line2_h = row.value_widget.preferred_height().unwrap_or(44.0);
-            let row_h = line1_h + gap_between_lines + line2_h;
-            total_rows_h += row_h;
-            if i < self.rows.len() - 1 {
-                total_rows_h += gap_between_rows;
-            }
-        }
-        let top_limit = origin.y + pad_y;
-        let bottom_limit = top_limit + total_rows_h;
-
-        for (i, row) in self.rows.iter_mut().enumerate() {
-            let line2_h = row.value_widget.preferred_height().unwrap_or(44.0);
-            let row_h = line1_h + gap_between_lines + line2_h;
-            row.layout_height = row_h;
-            row.natural_y = curr_y;
-
-            let mut row_y = curr_y;
-            if Some(i) == self.active_drag_index {
-                row_y = (curr_y + self.drag_y_offset).clamp(top_limit, (bottom_limit - row_h).max(top_limit));
-            }
-            row.layout_y = row_y;
-
-            // Line 1: Label, Type, Remove
-            let key_x = origin.x + pad_x + left_shift;
-            row.key_input.layout(Point { x: key_x, y: row_y }, LayoutConstraints::new(key_w, key_w, line1_h, line1_h), ctx);
-
-            let type_x = key_x + key_w + gap_x;
-            row.type_dropdown.layout(Point { x: type_x, y: row_y }, LayoutConstraints::new(type_w, type_w, line1_h, line1_h), ctx);
-
-            let remove_x = type_x + type_w + gap_x;
-            row.remove_button.layout(Point { x: remove_x, y: row_y }, LayoutConstraints::new(remove_w, remove_w, line1_h, line1_h), ctx);
-
-            // Line 2: Value Control Widget
-            let value_y = row_y + line1_h + gap_between_lines;
-            let value_x = origin.x + pad_x + left_shift;
-            row.value_widget.layout(Point { x: value_x, y: value_y }, LayoutConstraints::new(usable_w, usable_w, line2_h, line2_h), ctx);
-
-            link_child(self_ptr, self_id, &mut row.key_input, ctx);
-            link_child(self_ptr, self_id, &mut row.type_dropdown, ctx);
-            match &mut row.value_widget {
-                InstancedWidget::TextBox(tb) => link_child(self_ptr, self_id, tb, ctx),
-                InstancedWidget::Spinbox(sb) => link_child(self_ptr, self_id, sb, ctx),
-                InstancedWidget::Toggle(tg) => link_child(self_ptr, self_id, tg, ctx),
-                InstancedWidget::Slider(sl) => link_child(self_ptr, self_id, sl, ctx),
-            }
-            link_child(self_ptr, self_id, &mut row.remove_button, ctx);
-
-            curr_y += row_h + gap_between_rows;
-        }
-
-        // Lay out add button
-        let add_btn_w = 120.0f32.min(usable_w);
-        let add_btn_h = 36.0;
-        let add_x = origin.x + pad_x + left_shift;
-        self.add_button.layout(Point { x: add_x, y: curr_y }, LayoutConstraints::new(add_btn_w, add_btn_w, add_btn_h, add_btn_h), ctx);
-        link_child(self_ptr, self_id, &mut self.add_button, ctx);
-    }
-
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = self.extra_quads();
-        if let Some(hq) = self.highlight_quad(ctx) {
-            if hq.4 != colors::HIGHLIGHT_SECONDARY {
-                quads.push(hq);
-            }
-        }
-
-        let theme = colors::active_theme();
-
-        // 1. Draw all non-dragged rows first
-        for (idx, row) in self.rows.iter().enumerate() {
-            if Some(idx) == self.active_drag_index {
-                continue;
-            }
-            let handle_quads = self.get_drag_handle_quads(idx, theme);
-            quads.extend(handle_quads);
-
-            quads.extend(row.key_input.all_quads(ctx));
-            quads.extend(row.type_dropdown.all_quads(ctx));
-            quads.extend(row.value_widget.all_quads(ctx));
-            quads.extend(row.remove_button.all_quads(ctx));
-        }
-
-        // 2. Draw active dragged row on top!
-        if let Some(idx) = self.active_drag_index {
-            if idx < self.rows.len() {
-                let row = &self.rows[idx];
-                
-                let (rx, _, rw, _) = self.rect();
-                let row_y = row.layout_y;
-                let row_h = row.layout_height;
-                let pad_x = 8.0;
-                let bg_color = [theme.surface_bg[0], theme.surface_bg[1], theme.surface_bg[2], 0.95];
-                let border_color = theme.primary_accent;
-                
-                // Add a backing plate/shadow for the dragged row
-                quads.push((rx + pad_x, row_y - 2.0, rw - 2.0 * pad_x, row_h + 4.0, [0.0, 0.0, 0.0, 0.25])); // shadow
-                quads.push((rx + pad_x, row_y - 1.0, rw - 2.0 * pad_x, row_h + 2.0, bg_color)); // background
-                quads.push((rx + pad_x, row_y - 1.0, rw - 2.0 * pad_x, 1.0, border_color)); // top border
-                quads.push((rx + pad_x, row_y + row_h + 1.0, rw - 2.0 * pad_x, 1.0, border_color)); // bottom border
-
-                let handle_quads = self.get_drag_handle_quads(idx, theme);
-                quads.extend(handle_quads);
-
-                quads.extend(row.key_input.all_quads(ctx));
-                quads.extend(row.type_dropdown.all_quads(ctx));
-                quads.extend(row.value_widget.all_quads(ctx));
-                quads.extend(row.remove_button.all_quads(ctx));
-            }
-        }
-
-        quads.extend(self.add_button.all_quads(ctx));
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        Vec::new()
-    }
-
-    fn text_labels_with_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        // 1. Draw non-dragged first
-        for (idx, row) in self.rows.iter().enumerate() {
-            if Some(idx) == self.active_drag_index {
-                continue;
-            }
-            labels.extend(row.key_input.text_labels_with_bounds(ctx));
-            labels.extend(row.type_dropdown.text_labels_with_bounds(ctx));
-            labels.extend(row.value_widget.text_labels_with_bounds(ctx));
-            labels.extend(row.remove_button.text_labels_with_bounds(ctx));
-        }
-
-        // 2. Draw active dragged row last (on top)
-        if let Some(idx) = self.active_drag_index {
-            if idx < self.rows.len() {
-                let row = &self.rows[idx];
-                labels.extend(row.key_input.text_labels_with_bounds(ctx));
-                labels.extend(row.type_dropdown.text_labels_with_bounds(ctx));
-                labels.extend(row.value_widget.text_labels_with_bounds(ctx));
-                labels.extend(row.remove_button.text_labels_with_bounds(ctx));
-            }
-        }
-
-        labels.extend(self.add_button.text_labels_with_bounds(ctx));
-        labels
-    }
-
-    fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        let font = self.widget_font();
-        for l in self.text_labels() {
-            labels.push((l, font.clone(), None));
-        }
-
-        // 1. Draw non-dragged first
-        for (idx, row) in self.rows.iter().enumerate() {
-            if Some(idx) == self.active_drag_index {
-                continue;
-            }
-            labels.extend(row.key_input.text_labels_with_font_and_bounds(ctx));
-            labels.extend(row.type_dropdown.text_labels_with_font_and_bounds(ctx));
-            labels.extend(row.value_widget.text_labels_with_font_and_bounds(ctx));
-            labels.extend(row.remove_button.text_labels_with_font_and_bounds(ctx));
-        }
-
-        // 2. Draw active dragged row last (on top)
-        if let Some(idx) = self.active_drag_index {
-            if idx < self.rows.len() {
-                let row = &self.rows[idx];
-                labels.extend(row.key_input.text_labels_with_font_and_bounds(ctx));
-                labels.extend(row.type_dropdown.text_labels_with_font_and_bounds(ctx));
-                labels.extend(row.value_widget.text_labels_with_font_and_bounds(ctx));
-                labels.extend(row.remove_button.text_labels_with_font_and_bounds(ctx));
-            }
-        }
-
-        labels.extend(self.add_button.text_labels_with_font_and_bounds(ctx));
-        labels
-    }
-
-    fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
-        // 1. Intercept events if add popover is open
-        if self.add_popover_open {
-            let (bx, by, bw, bh) = self.add_button.rect();
-            let dy = by + bh;
-            let dh = 4.0 * 24.0;
-            match event {
-                Event::PointerMove { x, y, .. } => {
-                    if *x >= bx && *x <= bx + bw && *y >= dy && *y <= dy + dh {
-                        let idx = ((*y - dy) / 24.0).floor() as usize;
-                        if idx < 4 {
-                            self.add_popover_hovered_idx = Some(idx);
-                            return true;
-                        }
-                    }
-                    self.add_popover_hovered_idx = None;
-                }
-                Event::MouseButton { button, state, x, y, .. } => {
-                    if *button == MouseButton::Left && *state == ElementState::Pressed {
-                        if *x >= bx && *x <= bx + bw && *y >= dy && *y <= dy + dh {
-                            let idx = ((*y - dy) / 24.0).floor() as usize;
-                            if idx < 4 {
-                                let new_type = match idx {
-                                    1 => "Spinbox".to_string(),
-                                    2 => "Toggle".to_string(),
-                                    3 => "Slider".to_string(),
-                                    _ => "TextBox".to_string(),
-                                };
-                                let default_val = match idx {
-                                    1 => "0".to_string(),
-                                    2 => "false".to_string(),
-                                    3 => "0.5".to_string(),
-                                    _ => "".to_string(),
-                                };
-                                let new_row = MultiControlRow::new("new_widget".to_string(), new_type, default_val);
-                                self.rows.push(new_row);
-                                self.save_to_config();
-                                self.just_changed = true;
-                            }
-                            self.add_popover_open = false;
-                            self.add_popover_hovered_idx = None;
-                            return true;
-                        } else {
-                            // Clicked outside add popover, close it
-                            self.add_popover_open = false;
-                            self.add_popover_hovered_idx = None;
-                            return true;
-                        }
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        let mut handled = false;
-
-        // 1.5 Handle drag events and drag handle interactions
-        let mut drag_handled = false;
-        match event {
-            Event::PointerMove { x, y, .. } => {
-                let (rx, _, _, _) = self.rect();
-                let pad_x = 8.0;
-                let drag_handle_w = 20.0;
-                let old_hovered = self.hovered_drag_index;
-                self.hovered_drag_index = None;
-                
-                if *x >= rx + pad_x && *x <= rx + pad_x + drag_handle_w {
-                    for (i, row) in self.rows.iter().enumerate() {
-                        if *y >= row.layout_y && *y <= row.layout_y + row.layout_height {
-                            self.hovered_drag_index = Some(i);
-                            break;
-                        }
-                    }
-                }
-                
-                if old_hovered != self.hovered_drag_index {
-                    drag_handled = true;
-                }
-            }
-            Event::MouseButton { button: MouseButton::Left, state: ElementState::Pressed, x, y, .. } => {
-                let (rx, _, _, _) = self.rect();
-                let pad_x = 8.0;
-                let drag_handle_w = 20.0;
-                if *x >= rx + pad_x && *x <= rx + pad_x + drag_handle_w {
-                    for (i, row) in self.rows.iter().enumerate() {
-                        if *y >= row.layout_y && *y <= row.layout_y + row.layout_height {
-                            self.active_drag_index = Some(i);
-                            self.drag_start_mouse_y = *y;
-                            self.drag_y_offset = 0.0;
-                            drag_handled = true;
-                            break;
-                        }
-                    }
-                }
-            }
-            Event::DragStart { .. } => {
-                if self.active_drag_index.is_some() {
-                    drag_handled = true;
-                }
-            }
-            Event::DragUpdate { y, .. } => {
-                if let Some(mut i) = self.active_drag_index {
-                    self.drag_y_offset = *y - self.drag_start_mouse_y;
-                    
-                    let gap = 12.0; // gap_between_rows
-                    let mut swapped = true;
-                    while swapped {
-                        swapped = false;
-                        let row_h = self.rows[i].layout_height;
-                        let mid_y = self.rows[i].natural_y + self.drag_y_offset + row_h * 0.5;
-                        
-                        if i > 0 {
-                            let mid_above = self.rows[i-1].natural_y + self.rows[i-1].layout_height * 0.5;
-                            if mid_y < mid_above {
-                                self.drag_start_mouse_y -= self.rows[i-1].layout_height + gap;
-                                self.rows.swap(i, i-1);
-                                
-                                // Recalculate natural_y for all rows to prevent infinite swap loops
-                                let top_limit = self.rect().1 + 8.0;
-                                let mut curr_y = top_limit;
-                                for r in &mut self.rows {
-                                    r.natural_y = curr_y;
-                                    curr_y += r.layout_height + gap;
-                                }
-
-                                i -= 1;
-                                self.active_drag_index = Some(i);
-                                self.drag_y_offset = *y - self.drag_start_mouse_y;
-                                swapped = true;
-                                continue;
-                            }
-                        }
-                        if i < self.rows.len() - 1 {
-                            let mid_below = self.rows[i+1].natural_y + self.rows[i+1].layout_height * 0.5;
-                            if mid_y > mid_below {
-                                self.drag_start_mouse_y += self.rows[i+1].layout_height + gap;
-                                self.rows.swap(i, i+1);
-                                
-                                // Recalculate natural_y for all rows to prevent infinite swap loops
-                                let top_limit = self.rect().1 + 8.0;
-                                let mut curr_y = top_limit;
-                                for r in &mut self.rows {
-                                    r.natural_y = curr_y;
-                                    curr_y += r.layout_height + gap;
-                                }
-
-                                i += 1;
-                                self.active_drag_index = Some(i);
-                                self.drag_y_offset = *y - self.drag_start_mouse_y;
-                                swapped = true;
-                                continue;
-                            }
-                        }
-                    }
-                    drag_handled = true;
-                }
-            }
-            Event::DragEnd | Event::MouseButton { button: MouseButton::Left, state: ElementState::Released, .. } => {
-                if self.active_drag_index.is_some() {
-                    self.active_drag_index = None;
-                    self.drag_y_offset = 0.0;
-                    self.save_to_config();
-                    self.just_changed = true;
-                    drag_handled = true;
-                }
-            }
-            _ => {}
-        }
-        
-        if drag_handled {
-            return true;
-        }
-
-        // If we are actively dragging, swallow all other events
-        if self.active_drag_index.is_some() {
-            return true;
-        }
-
-        // 2. Intercept row dropdown open popovers first
-        for row in &mut self.rows {
-            if row.type_dropdown.open {
-                if row.type_dropdown.handle_event(event, ctx) {
-                    handled = true;
-                }
-            }
-        }
-
-        if !handled {
-            if self.add_button.handle_event(event, ctx) {
-                handled = true;
-            }
-
-            let mut row_to_remove = None;
-            for (i, row) in self.rows.iter_mut().enumerate() {
-                if row.key_input.handle_event(event, ctx) {
-                    handled = true;
-                }
-                if row.type_dropdown.handle_event(event, ctx) {
-                    handled = true;
-                }
-                if row.value_widget.handle_event(event, ctx) {
-                    handled = true;
-                }
-                if row.remove_button.handle_event(event, ctx) {
-                    handled = true;
-                }
-                if row.remove_button.take_click() {
-                    row_to_remove = Some(i);
-                    handled = true;
-                }
-            }
-
-            if let Some(idx) = row_to_remove {
-                self.rows.remove(idx);
-                self.save_to_config();
-                self.just_changed = true;
-            }
-        }
-
-        if self.add_button.take_click() {
-            self.add_popover_open = !self.add_popover_open;
-            self.add_popover_hovered_idx = None;
-            handled = true;
-        }
-
-        let mut type_changed = false;
-        let mut value_changed = false;
-        let mut key_changed = false;
-
-        for row in &mut self.rows {
-            if row.key_input.take_change() {
-                key_changed = true;
-            }
-            if row.type_dropdown.take_change() {
-                type_changed = true;
-                let new_type_idx = row.type_dropdown.selected;
-                let old_val = row.value_widget.get_value_string().unwrap_or_default();
-                row.value_widget = match new_type_idx {
-                    1 => {
-                        let val_i = old_val.parse::<i32>().unwrap_or(0);
-                        InstancedWidget::Spinbox(Spinbox::new(val_i, 0, 1000000, 1))
-                    }
-                    2 => {
-                        let toggled = old_val.trim().to_lowercase() == "true" || old_val == "1";
-                        let mut tg = Toggle::new();
-                        tg.set_toggled(toggled);
-                        InstancedWidget::Toggle(tg)
-                    }
-                    3 => {
-                        let mut sl = Slider::new();
-                        sl.set_value_string(&old_val);
-                        InstancedWidget::Slider(sl)
-                    }
-                    _ => {
-                        InstancedWidget::TextBox(TextBox::new(old_val))
-                    }
-                };
-            }
-            if row.value_widget.take_change() {
-                value_changed = true;
-            }
-        }
-
-        if key_changed || type_changed || value_changed {
-            self.save_to_config();
-            self.just_changed = true;
-        }
-
-        if !handled {
-            match event {
-                Event::PointerMove { x, y, .. } => {
-                    let is_hit = self.hit_test(*x, *y, ctx);
-                    let was = self.hovered();
-                    self.set_hovered(is_hit);
-                    if was != is_hit {
-                        handled = true;
-                    }
-                }
-                _ => {}
-            }
-        }
-
-        handled
-    }
-
-    fn take_change(&mut self) -> bool {
-        let ret = self.just_changed;
-        self.just_changed = false;
-        ret
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::MouseButton { button, state, x: px, y: py, local_x: px, local_y: py }, ctx)
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::KeyInput(event.clone()), ctx)
-    }
-
-    fn cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        self.handle_event(&Event::PointerMove { x: px, y: py, local_x: px, local_y: py }, ctx)
-    }
-
-    fn unfocus(&mut self) {
-        self.add_popover_open = false;
-        self.add_popover_hovered_idx = None;
-        for row in &mut self.rows {
-            row.key_input.unfocus();
-            row.type_dropdown.open = false;
-            row.value_widget.unfocus();
-        }
-    }
-
-    fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
-        if let Some((x, y, w, h)) = self.popover_rect() {
-            if px >= x && px <= x + w && py >= y && py <= y + h {
-                return true;
-            }
-        }
-        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 popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        for row in &self.rows {
-            if let Some(r) = row.type_dropdown.popover_rect() {
-                return Some(r);
-            }
-        }
-        if self.add_popover_open {
-            let (bx, by, bw, bh) = self.add_button.rect();
-            Some((bx, by + bh, bw, 4.0 * 24.0))
-        } else {
-            None
-        }
-    }
-
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        for row in &self.rows {
-            if row.type_dropdown.popover_rect().is_some() {
-                row.type_dropdown.render_popover(pc);
-                return;
-            }
-        }
-        if !self.add_popover_open { return; }
-
-        let (bx, by, bw, bh) = self.add_button.rect();
-        let dy = by + bh;
-        let dh = 4.0 * 24.0;
-        let options = vec!["TextBox", "Spinbox", "Toggle", "Slider"];
-
-        // Soft layered drop shadows
-        pc.rect([0.02, 0.02, 0.05, 0.15], bx + 1.0, dy + 1.0, bw, dh);
-        pc.rect([0.02, 0.02, 0.05, 0.08], bx + 3.0, dy + 3.0, bw, dh);
-        pc.rect([0.02, 0.02, 0.05, 0.04], bx + 5.0, dy + 5.0, bw, dh);
-
-        let theme = colors::active_theme();
-        pc.rect(theme.surface_border, bx, dy, bw, dh);
-        pc.rect(theme.surface_bg, bx + 1.0, dy + 1.0, bw - 2.0, dh - 2.0);
-
-        if let Some(h_idx) = self.add_popover_hovered_idx {
-            let iy = dy + h_idx as f32 * 24.0;
-            pc.rect(theme.primary_accent, bx + 2.0, iy + 2.0, bw - 4.0, 20.0);
-        }
-
-        for (idx, opt) in options.iter().enumerate() {
-            let iy = crate::layout::align_text_y(dy + idx as f32 * 24.0, 24.0, 12.0, 0.0);
-            let text_color = if self.add_popover_hovered_idx == Some(idx) {
-                [0xff, 0xff, 0xff]
-            } else {
-                [0xcc, 0xcc, 0xd4]
-            };
-            let color_f32 = [
-                text_color[0] as f32 / 255.0,
-                text_color[1] as f32 / 255.0,
-                text_color[2] as f32 / 255.0,
-                1.0,
-            ];
-            pc.text(opt, bx + 8.0, iy, 12.0, color_f32);
-        }
-    }
-}
-
-impl Control for MultiControl {}
-
-fn get_application_config_path() -> std::path::PathBuf {
-    let base_dir = crate::config::get_config_path()
-        .parent()
-        .map(|p| p.to_path_buf())
-        .unwrap_or_else(|| {
-            let home = std::env::var("HOME").unwrap_or_default();
-            std::path::PathBuf::from(home).join(".config").join("cce")
-        });
-
-    let mut app_name = std::env::current_exe()
-        .ok()
-        .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
-        .unwrap_or_else(|| "this-application".to_string());
-
-    if app_name.ends_with(" (deleted)") {
-        app_name = app_name[..app_name.len() - " (deleted)".len()].to_string();
-    }
-
-    base_dir.join(app_name).join("this-application.json")
-}
-
-fn save_config(name: &str, controls: &[InstancedControl]) {
-    let path = get_application_config_path();
-    if let Some(parent) = path.parent() {
-        let _ = std::fs::create_dir_all(parent);
-    }
-
-    let mut map = if path.exists() {
-        let content = std::fs::read_to_string(&path).unwrap_or_default();
-        serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&content)
-            .unwrap_or_default()
-    } else {
-        serde_json::Map::new()
-    };
-
-    if let Ok(json_val) = serde_json::to_value(controls) {
-        map.insert(name.to_string(), json_val);
-    }
-
-    if let Ok(updated_content) = serde_json::to_string_pretty(&map) {
-        let _ = std::fs::write(&path, updated_content);
-    }
-}
-
-fn load_config(name: &str) -> Vec<InstancedControl> {
-    let path = get_application_config_path();
-    
-    if path.exists() {
-        if let Ok(content) = std::fs::read_to_string(&path) {
-            if let Ok(serde_json::Value::Object(map)) = serde_json::from_str(&content) {
-                if let Some(val) = map.get(name) {
-                    if let Ok(controls) = serde_json::from_value::<Vec<InstancedControl>>(val.clone()) {
-                        return controls;
-                    }
-                }
-            }
-        }
-    }
-
-    Vec::new()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_instanced_control_serialization() {
-        let controls = vec![
-            InstancedControl {
-                key: "width".to_string(),
-                control_type: "Spinbox".to_string(),
-                value: "42".to_string(),
-            },
-            InstancedControl {
-                key: "label".to_string(),
-                control_type: "TextBox".to_string(),
-                value: "hello".to_string(),
-            },
-        ];
-
-        let serialized = serde_json::to_string(&controls).unwrap();
-        let deserialized: Vec<InstancedControl> = serde_json::from_str(&serialized).unwrap();
-        assert_eq!(controls, deserialized);
-    }
-
-    static ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
-
-    #[test]
-    fn test_save_load_config_files() {
-        let _guard = ENV_MUTEX.lock().unwrap();
-        let temp_dir = std::env::temp_dir().join("cce_test_home");
-        let _ = std::fs::create_dir_all(&temp_dir);
-        let old_home = std::env::var("HOME").ok();
-        std::env::set_var("HOME", temp_dir.to_str().unwrap());
-
-        let controls = vec![
-            InstancedControl {
-                key: "x_offset".to_string(),
-                control_type: "TextBox".to_string(),
-                value: "10".to_string(),
-            },
-        ];
-
-        let name = "my_multicontrol";
-        save_config(name, &controls);
-
-        let loaded = load_config(name);
-        assert_eq!(controls, loaded);
-
-        let path = get_application_config_path();
-        assert!(path.exists());
-
-        let _ = std::fs::remove_dir_all(&temp_dir);
-        if let Some(h) = old_home {
-            std::env::set_var("HOME", h);
-        }
-    }
-
-    #[test]
-    fn test_popover_interaction() {
-        let _guard = ENV_MUTEX.lock().unwrap();
-        let mut dummy = crate::context::UiContext::new();
-        let temp_dir = std::env::temp_dir().join("cce_test_home_popover");
-        let _ = std::fs::create_dir_all(&temp_dir);
-        let old_home = std::env::var("HOME").ok();
-        std::env::set_var("HOME", temp_dir.to_str().unwrap());
-
-        let mut mc = MultiControl::new("test_mc".to_string());
-        mc.set_rect(10.0, 10.0, 300.0, 200.0);
-        mc.layout(Point { x: 10.0, y: 10.0 }, LayoutConstraints::new(300.0, 300.0, 200.0, 200.0), &mut dummy);
-
-        assert!(!mc.add_popover_open);
-
-        let (bx, by, _bw, bh) = mc.add_button.rect();
-        let clicked = mc.mouse_input(MouseButton::Left, ElementState::Pressed, bx + 5.0, by + 5.0, &mut dummy);
-        assert!(clicked);
-        let released = mc.mouse_input(MouseButton::Left, ElementState::Released, bx + 5.0, by + 5.0, &mut dummy);
-        assert!(released);
-        assert!(mc.add_popover_open);
-
-        let dy = by + bh;
-        let hover_y = dy + 60.0;
-        let moved = mc.cursor_moved(bx + 5.0, hover_y, &mut dummy);
-        assert!(moved);
-        assert_eq!(mc.add_popover_hovered_idx, Some(2));
-
-        let clicked_item = mc.mouse_input(MouseButton::Left, ElementState::Pressed, bx + 5.0, hover_y, &mut dummy);
-        assert!(clicked_item);
-        assert!(!mc.add_popover_open);
-        assert_eq!(mc.rows.len(), 1);
-
-        let row = &mc.rows[0];
-        assert_eq!(row.key_input.get_value_string().unwrap(), "new_widget");
-        match &row.value_widget {
-            InstancedWidget::Toggle(_) => {},
-            _ => panic!("Expected Toggle widget"),
-        }
-
-        let _ = std::fs::remove_dir_all(&temp_dir);
-        if let Some(h) = old_home {
-            std::env::set_var("HOME", h);
-        }
-    }
-
-    #[test]
-    fn test_multicontrol_drag_reorder() {
-        let _guard = ENV_MUTEX.lock().unwrap();
-        let mut dummy = crate::context::UiContext::new();
-        let temp_dir = std::env::temp_dir().join("cce_test_home_drag");
-        let _ = std::fs::remove_dir_all(&temp_dir);
-        let _ = std::fs::create_dir_all(&temp_dir);
-        let old_home = std::env::var("HOME").ok();
-        std::env::set_var("HOME", temp_dir.to_str().unwrap());
-
-        let mut mc = MultiControl::new("test_mc_drag".to_string());
-        // Add two rows
-        mc.rows.push(MultiControlRow::new("param1".to_string(), "Spinbox".to_string(), "10".to_string()));
-        mc.rows.push(MultiControlRow::new("param2".to_string(), "TextBox".to_string(), "val".to_string()));
-        mc.save_to_config();
-
-        // Perform initial layout
-        mc.set_rect(10.0, 10.0, 300.0, 250.0);
-        mc.layout(Point { x: 10.0, y: 10.0 }, LayoutConstraints::new(300.0, 300.0, 250.0, 250.0), &mut dummy);
-
-        assert_eq!(mc.rows[0].key_input.get_value_string().unwrap(), "param1");
-        assert_eq!(mc.rows[1].key_input.get_value_string().unwrap(), "param2");
-
-        // Click on the drag handle of the first row (param1)
-        // Drag handle x is in: rx + pad_x (10 + 8 = 18) to rx + pad_x + drag_handle_w (18 + 20 = 38).
-        // Let's click at x = 25, y = mc.rows[0].layout_y + 5.
-        let click_x = 25.0;
-        let click_y = mc.rows[0].layout_y + 5.0;
-
-        let handled_press = mc.mouse_input(MouseButton::Left, ElementState::Pressed, click_x, click_y, &mut dummy);
-        assert!(handled_press);
-        assert_eq!(mc.active_drag_index, Some(0));
-
-        // Drag down to swap with the second row (param2)
-        // Row 1 starts around natural_y + height + gap. Let's move mouse to mid-point of row 1.
-        let target_y = mc.rows[1].natural_y + mc.rows[1].layout_height * 0.5 + 5.0;
-        
-        let drag_update_evt = Event::DragUpdate {
-            dx: 0.0,
-            dy: target_y - click_y,
-            x: click_x,
-            y: target_y,
-            local_x: click_x - 10.0,
-            local_y: target_y - 10.0,
-        };
-        let handled_drag = mc.handle_event(&drag_update_evt, &mut dummy);
-        assert!(handled_drag);
-
-        // Verify that the swap occurred!
-        assert_eq!(mc.active_drag_index, Some(1));
-        assert_eq!(mc.rows[0].key_input.get_value_string().unwrap(), "param2");
-        assert_eq!(mc.rows[1].key_input.get_value_string().unwrap(), "param1");
-
-        // End drag
-        let handled_end = mc.handle_event(&Event::DragEnd, &mut dummy);
-        assert!(handled_end);
-        assert_eq!(mc.active_drag_index, None);
-
-        // Load config from disk and verify the new order is persisted!
-        let loaded = load_config("test_mc_drag");
-        assert_eq!(loaded.len(), 2);
-        assert_eq!(loaded[0].key, "param2");
-        assert_eq!(loaded[1].key, "param1");
-
-        let _ = std::fs::remove_dir_all(&temp_dir);
-        if let Some(h) = old_home {
-            std::env::set_var("HOME", h);
-        }
-    }
-}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index a454d13..aa390a2 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -834,16 +834,14 @@ pub use self::core::focus::link_parent_child;
 pub use self::input::{
     Button, TextBox, Spinbox, Dropdown, Checkbox, Toggle, Slider, RangeSlider,
     ColorSelector, Finger, Trackpad, Canvas, get_font_db, ActiveThumb, FontSelector,
-    ButtonStrip, MultiControl, InstancedControl, InstancedWidget, MultiControlRow,
-    KeybindsControl, KeybindRow, KeybindRecorder, Ramp, RampKey, ColorRamp, ColorRampKey
+    ButtonStrip, KeybindRecorder, Ramp, RampKey, ColorRamp, ColorRampKey
 };
 pub use self::container::{
     Container, ContainerLayout, OverlayLayout, ManualLayout, VerticalLayout, GridLayout, AdaptiveGridLayout,
     ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
     SectionContainer, Header, ContentBg, ParametersBg, List, ListColumn, ListRow, ColumnWidth,
     ScrollBox, MenuBar, Spreadsheet, Breadcrumb, Plate,
-    Switcher, Layer, Page, Backplate, Paginator, ScrollBar, TreeList, TreeElement, ControlPanel,
-    SplitBox, SplitDirection
+    Switcher, Layer, Page, Backplate, Paginator, ScrollBar, TreeList, TreeElement, ControlPanel
 };
 pub use self::display::{
     TextLabel, Label, SectionHeader, StyledLabel, LabelPrim, TextItem, Svg, UsageBar,