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

commitd190c43b46d56bd3f8fb2cd62bb83c927a396cca
parent908b3e0e8d
authorLucas Galante <[email protected]>
date2026-06-06 00:06
Refactor widgets to Element trait, split widget.rs, and implement FontSelector (modified: src/color.rs, src/engine.rs, src/layout.rs and 8 others)

 src/color.rs              |    20 +
 src/engine.rs             |     8 +-
 src/layout.rs             |   427 +-
 src/main.rs               |    10 +-
 src/widget.rs             | 10734 --------------------------------------------
 src/widget/container.rs   |  5076 +++++++++++++++++++++
 src/widget/core.rs        |   685 +++
 src/widget/display.rs     |  2357 ++++++++++
 src/widget/input.rs       |  3076 +++++++++++++
 src/widget/json_layout.rs |    53 +-
 src/widget/mod.rs         |   370 ++
 11 files changed, 12020 insertions(+), 10796 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index ed61384..cb541cf 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -458,3 +458,23 @@ pub fn set_toggle_off_color(color: [f32; 4]) {
     }
 }
 
+#[derive(Debug, Clone, Copy)]
+pub struct Theme {
+    pub surface_bg: [f32; 4],
+    pub surface_border: [f32; 4],
+    pub primary_accent: [f32; 4],
+    pub press_overlay: [f32; 4],
+    pub hover_overlay: [f32; 4],
+}
+
+pub fn active_theme() -> Theme {
+    Theme {
+        surface_bg: [0.10, 0.10, 0.14, 0.95],
+        surface_border: [0.25, 0.25, 0.35, 0.8],
+        primary_accent: [0.20, 0.50, 0.75, 1.0],
+        press_overlay: [1.0, 1.0, 1.0, 0.15],
+        hover_overlay: [1.0, 1.0, 1.0, 0.08],
+    }
+}
+
+
diff --git a/src/engine.rs b/src/engine.rs
index f487f75..f51d8e0 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -452,7 +452,7 @@ pub fn plate_bevel_vertices(
     verts
 }
 
-pub fn widget_vertices(w: &dyn crate::widget::Widget, sw: f32, sh: f32, clip_circle: [f32; 3]) -> Vec<Vertex> {
+pub fn widget_vertices(w: &dyn crate::widget::Element, sw: f32, sh: f32, clip_circle: [f32; 3]) -> Vec<Vertex> {
     let (x, y, ww, h) = w.rect();
     let corners = w.rounded_corners();
     let mut verts = if corners != (false, false, false, false) {
@@ -469,7 +469,7 @@ pub fn widget_vertices(w: &dyn crate::widget::Widget, sw: f32, sh: f32, clip_cir
 }
 
 pub fn extra_quad_vertices(
-    w: &dyn crate::widget::Widget,
+    w: &dyn crate::widget::Element,
     qx: f32, qy: f32, qw: f32, qh: f32,
     sw: f32, sh: f32,
     qc: [f32; 4],
@@ -492,7 +492,7 @@ pub fn extra_quad_vertices(
 }
 
 pub fn extra_quad_vertices_clipped(
-    w: &dyn crate::widget::Widget,
+    w: &dyn crate::widget::Element,
     qx: f32, qy: f32, qw: f32, qh: f32,
     sw: f32, sh: f32,
     qc: [f32; 4],
@@ -1631,7 +1631,7 @@ impl<A: Application> PopupHandler for EngineState<A> {
     fn done(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _popup: &Popup) {
         for popover_ptr in crate::widget::popovers::get_active() {
             unsafe {
-                let popover = &mut *(popover_ptr as *mut dyn crate::widget::Widget);
+                let popover = &mut *(popover_ptr as *mut dyn crate::widget::Element);
                 popover.unfocus();
             }
         }
diff --git a/src/layout.rs b/src/layout.rs
index c513710..32f9612 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1,4 +1,4 @@
-use crate::widget::Widget;
+use crate::widget::Element;
 
 pub trait RenderTarget {
     fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32);
@@ -47,7 +47,7 @@ impl RenderTarget for PopoverCollector {
     }
 }
 
-pub fn render_widget<T: Widget + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32) {
+pub fn render_widget<T: Element + 'static>(pc: &mut dyn RenderTarget, w: &mut T, x: f32, y: f32, ww: f32, wh: f32) {
     w.set_rect(x, y, ww, wh);
     for (qx, qy, qw, qh, qc) in w.all_quads() {
         pc.rect(qc, qx, qy, qw, qh);
@@ -152,8 +152,8 @@ impl Column {
         pc.text(text, x, y, font_size, color);
     }
 
-    pub fn widget<T: Widget + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
-        let top_room = w.top_room();
+    pub fn widget<T: Element + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
+        let top_room = crate::widget::label_offset(w);
         let total_h = wh + top_room;
         let x = self.ax(x_off);
         let y = self.ay();
@@ -200,7 +200,7 @@ impl<'a> Row<'a> {
         self.cursor_x += width + self.spacing;
     }
 
-    pub fn widget<T: Widget + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
+    pub fn widget<T: Element + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
         render_widget(self.pc, w, self.base_x + self.cursor_x, self.y, ww, wh);
         self.cursor_x += ww + self.spacing;
     }
@@ -254,18 +254,18 @@ impl Section {
         pc.text(text, self.ax(x_off), self.ay() + y_off, font_size, color);
     }
 
-    pub fn widget<T: Widget + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
+    pub fn widget<T: Element + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
         w.set_row_rect(self.left + Self::ROW_PADDING_X, self.cw - 2.0 * Self::ROW_PADDING_X);
         let x = self.ax(x_off);
         let right_edge = self.left + self.cw - Self::ROW_PADDING_X;
         let clamped_w = ww.min((right_edge - x).max(0.0));
-        let top_room = w.top_room();
+        let top_room = crate::widget::label_offset(w);
         let total_h = wh + top_room;
         render_widget(pc, w, x, self.ay(), clamped_w, total_h);
         self.content_y += total_h;
     }
 
-    pub fn widget_full<T: Widget + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32) {
+    pub fn widget_full<T: Element + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32) {
         let x_off = 12.0;
         let ww = self.cw - 2.0 * (Self::ROW_PADDING_X + x_off); // cw - 40.0
         self.widget(pc, w, x_off, ww, wh);
@@ -349,8 +349,8 @@ impl Section {
         self.content_y + 20.0
     }
 
-    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> VStack<'a> {
-        VStack {
+    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> SectionVStack<'a> {
+        SectionVStack {
             section: self,
             pc,
             spacing,
@@ -358,14 +358,14 @@ impl Section {
     }
 }
 
-pub struct VStack<'a> {
+pub struct SectionVStack<'a> {
     section: &'a mut Section,
     pc: &'a mut dyn RenderTarget,
     spacing: f32,
 }
 
-impl<'a> VStack<'a> {
-    pub fn add_widget<T: Widget + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
+impl<'a> SectionVStack<'a> {
+    pub fn add_widget<T: Element + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
         self.section.widget(self.pc, w, Section::DEFAULT_MARGIN_X, ww, wh);
         self.section.spacing(self.spacing);
     }
@@ -427,18 +427,18 @@ impl Subsection {
         pc.text(text, self.ax(x_off), self.ay() + y_off, font_size, color);
     }
 
-    pub fn widget<T: Widget + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
+    pub fn widget<T: Element + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, x_off: f32, ww: f32, wh: f32) {
         w.set_row_rect(self.left + Self::ROW_PADDING_X, self.cw - 2.0 * Self::ROW_PADDING_X);
         let x = self.ax(x_off);
         let right_edge = self.left + self.cw - Self::ROW_PADDING_X;
         let clamped_w = ww.min((right_edge - x).max(0.0));
-        let top_room = w.top_room();
+        let top_room = crate::widget::label_offset(w);
         let total_h = wh + top_room;
         render_widget(pc, w, x, self.ay(), clamped_w, total_h);
         self.content_y += total_h;
     }
 
-    pub fn widget_full<T: Widget + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32) {
+    pub fn widget_full<T: Element + 'static>(&mut self, pc: &mut dyn RenderTarget, w: &mut T, wh: f32) {
         let x_off = 12.0;
         let ww = self.cw - 2.0 * (Self::ROW_PADDING_X + x_off);
         self.widget(pc, w, x_off, ww, wh);
@@ -522,8 +522,8 @@ impl Subsection {
         self.content_y + 20.0
     }
 
-    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> SubVStack<'a> {
-        SubVStack {
+    pub fn vstack<'a>(&'a mut self, pc: &'a mut dyn RenderTarget, spacing: f32) -> SubsectionVStack<'a> {
+        SubsectionVStack {
             subsection: self,
             pc,
             spacing,
@@ -531,14 +531,14 @@ impl Subsection {
     }
 }
 
-pub struct SubVStack<'a> {
+pub struct SubsectionVStack<'a> {
     subsection: &'a mut Subsection,
     pc: &'a mut dyn RenderTarget,
     spacing: f32,
 }
 
-impl<'a> SubVStack<'a> {
-    pub fn add_widget<T: Widget + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
+impl<'a> SubsectionVStack<'a> {
+    pub fn add_widget<T: Element + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
         self.subsection.widget(self.pc, w, Subsection::DEFAULT_MARGIN_X, ww, wh);
         self.subsection.spacing(self.spacing);
     }
@@ -750,7 +750,7 @@ impl Radial {
         }
     }
 
-    pub fn layout_widgets<T: Widget + 'static>(&self, widgets: &mut [&mut T]) {
+    pub fn layout_widgets<T: Element + 'static>(&self, widgets: &mut [&mut T]) {
         let mut active_idx = 0;
         for w in widgets.iter_mut() {
             if !w.layout_ignore() {
@@ -764,7 +764,7 @@ impl Radial {
         }
     }
 
-    pub fn layout_widget_ptors(&self, widgets: &[*mut (dyn Widget + 'static)]) {
+    pub fn layout_widget_ptors(&self, widgets: &[*mut (dyn Element + 'static)]) {
         let mut active_idx = 0;
         for &w_ptr in widgets {
             let w = unsafe { &mut *w_ptr };
@@ -950,16 +950,385 @@ impl<'a, P: RenderTarget + Default> PageLayoutBuilder<'a, P> {
         self
     }
 
-    pub fn add_section<F>(&mut self, final_pc: &mut P, mut render_fn: F)
+    pub fn add_section<F>(&mut self, final_pc: &mut P, label: &str, focused: bool, mut render_fn: F)
     where
-        F: FnMut(&mut P, f32, f32) -> f32,
+        F: FnMut(&mut SectionContext<'_, P>),
     {
         let mut dummy = P::default();
-        let wh = render_fn(&mut dummy, 0.0, 0.0);
+        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, self.section_width, label, focused);
+        render_fn(&mut dummy_ctx);
+        let wh = dummy_ctx.finish();
         let (rx, ry, _, _) = self.strategy.allocate(self.section_width, wh);
-        render_fn(final_pc, rx, ry);
+        let mut real_ctx = SectionContext::new(final_pc, rx, ry, self.section_width, label, focused);
+        render_fn(&mut real_ctx);
+        real_ctx.finish();
         self.idx += 1;
     }
+
+    pub fn add_section_with_width<F>(&mut self, final_pc: &mut P, width: f32, label: &str, focused: bool, mut render_fn: F)
+    where
+        F: FnMut(&mut SectionContext<'_, P>),
+    {
+        let mut dummy = P::default();
+        let mut dummy_ctx = SectionContext::new(&mut dummy, 0.0, 0.0, width, label, focused);
+        render_fn(&mut dummy_ctx);
+        let wh = dummy_ctx.finish();
+        let (rx, ry, _, _) = self.strategy.allocate(width, wh);
+        let mut real_ctx = SectionContext::new(final_pc, rx, ry, width, label, focused);
+        render_fn(&mut real_ctx);
+        real_ctx.finish();
+        self.idx += 1;
+    }
+}
+
+pub struct SectionContext<'a, P> {
+    pub pc: &'a mut P,
+    pub left: f32,
+    pub top: f32,
+    pub content_y: f32,
+    pub cw: f32,
+    pub label_width: f32,
+    pub focused: bool,
+}
+
+impl<'a, P: RenderTarget> SectionContext<'a, P> {
+    pub const ROW_PADDING_X: f32 = 8.0;
+    pub const DEFAULT_MARGIN_X: f32 = 12.0;
+    pub const DEFAULT_ROW_GAP: f32 = 8.0;
+
+    fn estimate_label_width(label: &str) -> f32 {
+        let mut width = 0.0;
+        for c in label.chars() {
+            let factor = match c {
+                'i' | 'l' | 't' | 'j' | 'f' | 'I' | ' ' | '.' | ',' | '!' | ';' | ':' | '\'' | '"' | '(' | ')' | '[' | ']' | '-' => 0.28,
+                'r' | 's' | 'J' | 'c' | 'z' => 0.42,
+                'm' | 'w' | 'M' | 'W' | '&' | '@' => 0.80,
+                'A'..='Z' => 0.68,
+                _ => 0.55,
+            };
+            width += factor * 14.0;
+        }
+        width
+    }
+
+    pub fn new(pc: &'a mut P, left: f32, top: f32, cw: f32, label: &str, focused: bool) -> Self {
+        let label_width = Self::estimate_label_width(label);
+        let label_x = left + (cw - label_width) / 2.0;
+        pc.text(label, label_x, top, 14.0, [0.83, 0.83, 0.83, 1.0]);
+        Self {
+            pc,
+            left,
+            top,
+            content_y: top + 19.0,
+            cw,
+            label_width,
+            focused,
+        }
+    }
+
+    pub fn ax(&self, x_off: f32) -> f32 {
+        let shift = if x_off >= 12.0 { 8.0 } else { 0.0 };
+        self.left + x_off + shift
+    }
+
+    pub fn ay(&self) -> f32 {
+        self.content_y
+    }
+
+    pub fn spacing(&mut self, dy: f32) {
+        self.content_y += dy;
+    }
+
+    pub fn text(&mut self, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
+        self.pc.text(text, self.ax(x_off), self.ay() + y_off, font_size, color);
+    }
+
+    pub fn widget<T: Element + 'static>(&mut self, w: &mut T, x_off: f32, ww: f32, wh: f32) {
+        w.set_row_rect(self.left + Self::ROW_PADDING_X, self.cw - 2.0 * Self::ROW_PADDING_X);
+        let x = self.ax(x_off);
+        let y = self.ay();
+        let right_edge = self.left + self.cw - Self::ROW_PADDING_X;
+        let clamped_w = ww.min((right_edge - x).max(0.0));
+        let top_room = crate::widget::label_offset(w);
+        let total_h = wh + top_room;
+        render_widget(self.pc, w, x, y, clamped_w, total_h);
+        self.content_y += total_h;
+    }
+
+    pub fn widget_full<T: Element + 'static>(&mut self, w: &mut T, wh: f32) {
+        let x_off = 12.0;
+        let ww = self.cw - 2.0 * (Self::ROW_PADDING_X + x_off); // cw - 40.0
+        self.widget(w, x_off, ww, wh);
+    }
+
+    pub fn separator(&mut self) {
+        let x = self.ax(Self::ROW_PADDING_X);
+        let y = self.ay();
+        self.pc.rect([0.18, 0.18, 0.27, 1.0], x, y, self.cw - 2.0 * Self::ROW_PADDING_X, 1.0);
+        self.content_y += 8.0;
+    }
+
+    pub fn rect(&mut self, color: [f32; 4], x_off: f32, w: f32, h: f32) {
+        self.pc.rect(color, self.ax(x_off), self.ay(), w, h);
+        self.content_y += h;
+    }
+
+    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
+        let margin_x = Self::ROW_PADDING_X + 12.0;
+        let usable_w = self.cw - 2.0 * margin_x;
+        if count == 0 {
+            return Vec::new();
+        }
+        let total_gap = gap * (count - 1) as f32;
+        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
+
+        let mut cols = Vec::with_capacity(count);
+        for i in 0..count {
+            let x = self.left + margin_x + i as f32 * (col_w + gap);
+            cols.push((x, col_w));
+        }
+        cols
+    }
+
+    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        let cols = self.row_layout(count, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            f(i, x, w);
+        }
+        self.content_y += h;
+    }
+
+    pub fn vstack(&mut self, spacing: f32) -> VStack<'_, 'a, P> {
+        VStack {
+            context: self,
+            spacing,
+        }
+    }
+
+    pub fn add_subsection<F>(&mut self, label: &str, focused: bool, mut render_fn: F)
+    where
+        F: FnMut(&mut SubsectionContext<'_, P>),
+    {
+        let left = self.ax(0.0) + Self::ROW_PADDING_X;
+        let top = self.content_y;
+        let cw = self.cw - 2.0 * Self::ROW_PADDING_X;
+
+        let mut sub_ctx = SubsectionContext::new(self.pc, left, top, cw, label, focused);
+        render_fn(&mut sub_ctx);
+        self.content_y = sub_ctx.finish();
+    }
+
+    pub fn finish(self) -> f32 {
+        let border: [f32; 4] = if self.focused {
+            [0.30, 0.50, 0.32, 1.0] // Focused green
+        } else {
+            [0.25, 0.25, 0.35, 1.0] // Default gray
+        };
+        let x = self.left + Self::ROW_PADDING_X;
+        let y = self.top + 7.0;
+        let w = self.cw - 2.0 * Self::ROW_PADDING_X;
+        let h = self.content_y - y;
+
+        let left_edge = x;
+        let right_edge = x + w;
+        if self.label_width > 0.0 {
+            let label_x = self.left + (self.cw - self.label_width) / 2.0;
+            let gap_margin = 6.0;
+            let gap_start = label_x - gap_margin;
+            let gap_end = label_x + self.label_width + gap_margin;
+            if gap_start > left_edge {
+                self.pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
+            }
+            if right_edge > gap_end {
+                self.pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
+            }
+        } else {
+            self.pc.rect(border, left_edge, y, w, 1.0);
+        }
+
+        self.pc.rect(border, x, y + h + 12.0, w, 1.0);
+        self.pc.rect(border, x, y, 1.0, h + 12.0);
+        self.pc.rect(border, x + w - 1.0, y, 1.0, h + 12.0);
+        self.content_y + 20.0
+    }
+}
+
+pub struct SubsectionContext<'a, P> {
+    pub pc: &'a mut P,
+    pub left: f32,
+    pub top: f32,
+    pub content_y: f32,
+    pub cw: f32,
+    pub label_width: f32,
+    pub focused: bool,
+}
+
+impl<'a, P: RenderTarget> SubsectionContext<'a, P> {
+    pub const ROW_PADDING_X: f32 = 8.0;
+    pub const DEFAULT_MARGIN_X: f32 = 12.0;
+    pub const DEFAULT_ROW_GAP: f32 = 8.0;
+
+    fn estimate_label_width(label: &str) -> f32 {
+        let mut width = 0.0;
+        for c in label.chars() {
+            let factor = match c {
+                'i' | 'l' | 't' | 'j' | 'f' | 'I' | ' ' | '.' | ',' | '!' | ';' | ':' | '\'' | '"' | '(' | ')' | '[' | ']' | '-' => 0.28,
+                'r' | 's' | 'J' | 'c' | 'z' => 0.42,
+                'm' | 'w' | 'M' | 'W' | '&' | '@' => 0.80,
+                'A'..='Z' => 0.68,
+                _ => 0.55,
+            };
+            width += factor * 12.0;
+        }
+        width
+    }
+
+    pub fn new(pc: &'a mut P, left: f32, top: f32, cw: f32, label: &str, focused: bool) -> Self {
+        let label_width = Self::estimate_label_width(label);
+        let label_x = left + (cw - label_width) / 2.0;
+        pc.text(label, label_x, top, 12.0, [0.53, 0.53, 0.60, 1.0]);
+        Self {
+            pc,
+            left,
+            top,
+            content_y: top + 17.0,
+            cw,
+            label_width,
+            focused,
+        }
+    }
+
+    pub fn ax(&self, x_off: f32) -> f32 {
+        let shift = if x_off >= 12.0 { 8.0 } else { 0.0 };
+        self.left + x_off + shift
+    }
+
+    pub fn ay(&self) -> f32 {
+        self.content_y
+    }
+
+    pub fn spacing(&mut self, dy: f32) {
+        self.content_y += dy;
+    }
+
+    pub fn text(&mut self, text: &str, x_off: f32, y_off: f32, font_size: f32, color: [f32; 4]) {
+        self.pc.text(text, self.ax(x_off), self.ay() + y_off, font_size, color);
+    }
+
+    pub fn widget<T: Element + 'static>(&mut self, w: &mut T, x_off: f32, ww: f32, wh: f32) {
+        w.set_row_rect(self.left + Self::ROW_PADDING_X, self.cw - 2.0 * Self::ROW_PADDING_X);
+        let x = self.ax(x_off);
+        let y = self.ay();
+        let right_edge = self.left + self.cw - Self::ROW_PADDING_X;
+        let clamped_w = ww.min((right_edge - x).max(0.0));
+        let top_room = crate::widget::label_offset(w);
+        let total_h = wh + top_room;
+        render_widget(self.pc, w, x, y, clamped_w, total_h);
+        self.content_y += total_h;
+    }
+
+    pub fn widget_full<T: Element + 'static>(&mut self, w: &mut T, wh: f32) {
+        let x_off = 12.0;
+        let ww = self.cw - 2.0 * (Self::ROW_PADDING_X + x_off);
+        self.widget(w, x_off, ww, wh);
+    }
+
+    pub fn separator(&mut self) {
+        let x = self.ax(Self::ROW_PADDING_X);
+        let y = self.ay();
+        self.pc.rect([0.15, 0.15, 0.22, 1.0], x, y, self.cw - 2.0 * Self::ROW_PADDING_X, 1.0);
+        self.content_y += 8.0;
+    }
+
+    pub fn rect(&mut self, color: [f32; 4], x_off: f32, w: f32, h: f32) {
+        self.pc.rect(color, self.ax(x_off), self.ay(), w, h);
+        self.content_y += h;
+    }
+
+    pub fn row_layout(&self, count: usize, gap: f32) -> Vec<(f32, f32)> {
+        let margin_x = Self::ROW_PADDING_X + 12.0;
+        let usable_w = self.cw - 2.0 * margin_x;
+        if count == 0 {
+            return Vec::new();
+        }
+        let total_gap = gap * (count - 1) as f32;
+        let col_w = (usable_w - total_gap).max(0.0) / count as f32;
+
+        let mut cols = Vec::with_capacity(count);
+        for i in 0..count {
+            let x = self.left + margin_x + i as f32 * (col_w + gap);
+            cols.push((x, col_w));
+        }
+        cols
+    }
+
+    pub fn row<F>(&mut self, count: usize, gap: f32, h: f32, mut f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        let cols = self.row_layout(count, gap);
+        for (i, &(x, w)) in cols.iter().enumerate() {
+            f(i, x, w);
+        }
+        self.content_y += h;
+    }
+
+    pub fn finish(self) -> f32 {
+        let border: [f32; 4] = if self.focused {
+            [0.22, 0.38, 0.24, 1.0]
+        } else {
+            [0.18, 0.18, 0.25, 1.0]
+        };
+        let x = self.left + Self::ROW_PADDING_X;
+        let y = self.top + 7.0;
+        let w = self.cw - 2.0 * Self::ROW_PADDING_X;
+        let h = self.content_y - y;
+
+        let left_edge = x;
+        let right_edge = x + w;
+        if self.label_width > 0.0 {
+            let label_x = self.left + (self.cw - self.label_width) / 2.0;
+            let gap_margin = 6.0;
+            let gap_start = label_x - gap_margin;
+            let gap_end = label_x + self.label_width + gap_margin;
+            if gap_start > left_edge {
+                self.pc.rect(border, left_edge, y, gap_start - left_edge, 1.0);
+            }
+            if right_edge > gap_end {
+                self.pc.rect(border, gap_end, y, right_edge - gap_end, 1.0);
+            }
+        } else {
+            self.pc.rect(border, left_edge, y, w, 1.0);
+        }
+
+        self.pc.rect(border, x, y + h + 12.0, w, 1.0);
+        self.pc.rect(border, x, y, 1.0, h + 12.0);
+        self.pc.rect(border, x + w - 1.0, y, 1.0, h + 12.0);
+        self.content_y + 20.0
+    }
+}
+
+pub struct VStack<'b, 'a, P> {
+    context: &'b mut SectionContext<'a, P>,
+    spacing: f32,
+}
+
+impl<'b, 'a, P: RenderTarget> VStack<'b, 'a, P> {
+    pub fn add_widget<T: Element + 'static>(&mut self, w: &mut T, ww: f32, wh: f32) {
+        self.context.widget(w, SectionContext::<P>::DEFAULT_MARGIN_X, ww, wh);
+        self.context.spacing(self.spacing);
+    }
+
+    pub fn add_row<F>(&mut self, count: usize, gap: f32, h: f32, f: F)
+    where
+        F: FnMut(usize, f32, f32),
+    {
+        self.context.row(count, gap, h, f);
+        self.context.spacing(self.spacing);
+    }
 }
 
 #[cfg(test)]
@@ -984,7 +1353,7 @@ mod tests {
         h: f32,
     }
 
-    impl Widget for MockWidget {
+    impl Element for MockWidget {
         fn rect(&self) -> (f32, f32, f32, f32) {
             (self.x, self.y, self.w, self.h)
         }
@@ -1007,7 +1376,7 @@ mod tests {
         top_room: f32,
     }
 
-    impl Widget for MockWidgetWithLabel {
+    impl Element for MockWidgetWithLabel {
         fn rect(&self) -> (f32, f32, f32, f32) {
             (self.x, self.y - self.top_room, self.w, self.h + self.top_room)
         }
diff --git a/src/main.rs b/src/main.rs
index 303724a..f293378 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
 use clear_ui::widget::{
     Button, Checkbox, ContentBg, Header, Panel, ProgressBar, RangeSlider, Sidebar, Slider, Spinbox,
-    StatusBar, TextLabel, Toggle, Widget, JsonLayoutWidget, JsonLayoutConfig,
+    StatusBar, TextLabel, Toggle, Element, JsonLayoutWidget, JsonLayoutConfig,
 };
 
 use glyphon::{
@@ -170,7 +170,7 @@ fn rounded_rect_vertices(
     rounded_rect_vertices_corners(x, y, ww, h, r, sw, sh, color, (true, true, true, true))
 }
 
-fn widget_vertices(w: &dyn Widget, sw: f32, sh: f32) -> Vec<Vertex> {
+fn widget_vertices(w: &dyn Element, sw: f32, sh: f32) -> Vec<Vertex> {
     let (x, y, ww, h) = w.rect();
     let corners = w.rounded_corners();
     if corners != (false, false, false, false) {
@@ -181,7 +181,7 @@ fn widget_vertices(w: &dyn Widget, sw: f32, sh: f32) -> Vec<Vertex> {
 }
 
 fn extra_quad_vertices(
-    w: &dyn Widget,
+    w: &dyn Element,
     qx: f32, qy: f32, qw: f32, qh: f32,
     sw: f32, sh: f32,
     qc: [f32; 4],
@@ -237,7 +237,7 @@ struct State {
     vertex_buffer: wgpu::Buffer,
     vertex_count: u32,
 
-    widgets: Vec<Box<dyn Widget>>,
+    widgets: Vec<Box<dyn Element>>,
     positions: Vec<(f32, f32, f32, f32)>,
 
     font_system: FontSystem,
@@ -380,7 +380,7 @@ impl State {
         let status_buffer = make_text_buffer(&mut font_system, "Click a button to interact", 12.0);
 
         let layout_mode = json_layout_config.is_some();
-        let mut widgets: Vec<Box<dyn Widget>> = Vec::new();
+        let mut widgets: Vec<Box<dyn Element>> = Vec::new();
         let mut positions = Vec::new();
         let json_layout = if let Some(ref config) = json_layout_config {
             let mut jl = JsonLayoutWidget::new(config);
diff --git a/src/widget.rs b/src/widget.rs
deleted file mode 100644
index a99d97f..0000000
--- a/src/widget.rs
+++ /dev/null
@@ -1,10734 +0,0 @@
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum ElementState {
-    Pressed,
-    Released,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum MouseButton {
-    Left,
-    Right,
-    Middle,
-    Back,
-    Forward,
-    Other(u16),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Position {
-    pub x: f64,
-    pub y: f64,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum MouseScrollDelta {
-    LineDelta(f32, f32),
-    PixelDelta(Position),
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub enum Key {
-    Named(NamedKey),
-    Character(String),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum NamedKey {
-    Backspace,
-    Tab,
-    Enter,
-    Escape,
-    Space,
-    ArrowDown,
-    ArrowLeft,
-    ArrowRight,
-    ArrowUp,
-    End,
-    Home,
-    PageDown,
-    PageUp,
-    Delete,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct KeyEvent {
-    pub state: ElementState,
-    pub logical_key: Key,
-    pub text: Option<String>,
-    pub repeat: bool,
-    pub ctrl: bool,
-    pub shift: bool,
-}
-
-pub mod json_layout;
-pub use json_layout::{JsonLayoutWidget, JsonLayoutConfig, JsonWidgetConfig, JsonPageConfig, JsonWidget};
-
-use crate::colors;
-
-pub mod focus {
-    use super::Widget;
-    use std::cell::Cell;
-
-    thread_local! {
-        static FOCUSED_WIDGET: Cell<Option<*mut (dyn Widget + 'static)>> = Cell::new(None);
-    }
-
-    pub fn set_focused(w: &mut dyn Widget) {
-        FOCUSED_WIDGET.with(|cell| {
-            let new_ptr = unsafe {
-                std::mem::transmute::<*mut dyn Widget, *mut (dyn Widget + 'static)>(w as *mut dyn Widget)
-            };
-            if let Some(old_ptr) = cell.get() {
-                let old_data = old_ptr as *mut () as usize;
-                let new_data = new_ptr as *mut () as usize;
-                if old_data != new_data {
-                    unsafe {
-                        (*old_ptr).unfocus();
-                    }
-                    cell.set(Some(new_ptr));
-                }
-            } else {
-                cell.set(Some(new_ptr));
-            }
-        });
-    }
-
-    pub fn is_focused(w: &dyn Widget) -> bool {
-        FOCUSED_WIDGET.with(|cell| {
-            if let Some(ptr) = cell.get() {
-                let current_data = ptr as *const () as usize;
-                let query_data = w as *const dyn Widget as *const () as usize;
-                current_data == query_data
-            } else {
-                false
-            }
-        })
-    }
-
-    pub fn clear_focus() {
-        FOCUSED_WIDGET.with(|cell| {
-            if let Some(ptr) = cell.take() {
-                unsafe {
-                    (*ptr).unfocus();
-                }
-            }
-        });
-    }
-
-    pub fn clear_if_matches(w: &dyn Widget) {
-        FOCUSED_WIDGET.with(|cell| {
-            if let Some(ptr) = cell.get() {
-                let current_data = ptr as *const () as usize;
-                let query_data = w as *const dyn Widget as *const () as usize;
-                if current_data == query_data {
-                    cell.set(None);
-                }
-            }
-        });
-    }
-
-    pub fn has_focus() -> bool {
-        FOCUSED_WIDGET.with(|cell| cell.get().is_some())
-    }
-
-    pub fn link_parent_child(parent: &mut dyn Widget, child: &mut dyn Widget) {
-        let parent_ptr = unsafe {
-            std::mem::transmute::<*mut dyn Widget, *mut (dyn Widget + 'static)>(parent as *mut dyn Widget)
-        };
-        let child_ptr = unsafe {
-            std::mem::transmute::<*mut dyn Widget, *mut (dyn Widget + 'static)>(child as *mut dyn Widget)
-        };
-        parent.add_child(child_ptr);
-        child.set_parent(Some(parent_ptr));
-    }
-
-    pub fn navigate_focus(key: &super::Key, ctrl: bool) -> bool {
-        FOCUSED_WIDGET.with(|cell| {
-            let ptr = match cell.get() {
-                Some(p) => p,
-                None => return false,
-            };
-
-            unsafe {
-                match (key, ctrl) {
-                    (super::Key::Character(c), true) if c == "u" || c == "U" => {
-                        if let Some(parent_ptr) = (*ptr).parent() {
-                            let parent_ref = &mut *parent_ptr;
-                            set_focused(parent_ref);
-                            parent_ref.focus();
-                            return true;
-                        }
-                    }
-                    (super::Key::Character(c), true) if c == "i" || c == "I" => {
-                        let mut children = (*ptr).children();
-                        if !children.is_empty() {
-                            let child_ref = &mut *children[0];
-                            set_focused(child_ref);
-                            child_ref.focus();
-                            return true;
-                        }
-                    }
-                    (super::Key::Character(c), true) if c == "j" || c == "J" => {
-                        if let Some(parent_ptr) = (*ptr).parent() {
-                            let mut siblings = (*parent_ptr).children();
-                            let current_idx = siblings.iter().position(|&x| {
-                                let a = x as *mut () as usize;
-                                let b = ptr as *mut () as usize;
-                                a == b
-                            });
-                            if let Some(idx) = current_idx {
-                                let next_idx = (idx + 1) % siblings.len();
-                                let sibling_ref = &mut *siblings[next_idx];
-                                set_focused(sibling_ref);
-                                sibling_ref.focus();
-                                return true;
-                            }
-                        }
-                    }
-                    (super::Key::Character(c), true) if c == "k" || c == "K" => {
-                        if let Some(parent_ptr) = (*ptr).parent() {
-                            let mut siblings = (*parent_ptr).children();
-                            let current_idx = siblings.iter().position(|&x| {
-                                let a = x as *mut () as usize;
-                                let b = ptr as *mut () as usize;
-                                a == b
-                            });
-                            if let Some(idx) = current_idx {
-                                let prev_idx = if idx == 0 { siblings.len() - 1 } else { idx - 1 };
-                                let sibling_ref = &mut *siblings[prev_idx];
-                                set_focused(sibling_ref);
-                                sibling_ref.focus();
-                                return true;
-                            }
-                        }
-                    }
-                    _ => {}
-                }
-            }
-            false
-        })
-    }
-}
-
-pub mod popovers {
-    use super::Widget;
-    use std::cell::RefCell;
-
-    thread_local! {
-        static ACTIVE_POPOVERS: RefCell<Vec<*const (dyn Widget + 'static)>> = RefCell::new(Vec::new());
-    }
-
-    pub fn clear() {
-        ACTIVE_POPOVERS.with(|list| {
-            list.borrow_mut().clear();
-        });
-    }
-
-    pub fn register(w: &(dyn Widget + 'static)) {
-        ACTIVE_POPOVERS.with(|list| {
-            let ptr = w as *const (dyn Widget + 'static);
-            let mut list = list.borrow_mut();
-            if !list.contains(&ptr) {
-                list.push(ptr);
-            }
-        });
-    }
-
-    pub fn get_active() -> Vec<*const (dyn Widget + 'static)> {
-        ACTIVE_POPOVERS.with(|list| {
-            list.borrow().clone()
-        })
-    }
-
-    pub fn is_coordinate_covered(query_address: usize, px: f32, py: f32) -> bool {
-        ACTIVE_POPOVERS.with(|list| {
-            let list = list.borrow();
-            for popover_ptr in list.iter() {
-                let current_data = *popover_ptr as *const () as usize;
-                if query_address == current_data {
-                    continue;
-                }
-                unsafe {
-                    if let Some(popover) = popover_ptr.as_ref() {
-                        if let Some((x, y, width, height)) = popover.popover_rect() {
-                            if px >= x && px <= x + width && py >= y && py <= y + height {
-                                return true;
-                            }
-                        }
-                    }
-                }
-            }
-            false
-        })
-    }
-}
-
-pub mod hover_animation {
-    use std::cell::RefCell;
-    use crate::colors;
-
-    #[derive(Debug, Clone)]
-    pub struct HoverState {
-        pub current_x: f32,
-        pub current_y: f32,
-        pub current_w: f32,
-        pub current_h: f32,
-        pub current_alpha: f32,
-
-        pub target_x: Option<f32>,
-        pub target_y: Option<f32>,
-        pub target_w: Option<f32>,
-        pub target_h: Option<f32>,
-        pub target_alpha: f32,
-
-        pub registered_this_frame: bool,
-        pub scroll_offset: f32,
-    }
-
-    impl HoverState {
-        fn new() -> Self {
-            Self {
-                current_x: 0.0,
-                current_y: 0.0,
-                current_w: 0.0,
-                current_h: 0.0,
-                current_alpha: 0.0,
-
-                target_x: None,
-                target_y: None,
-                target_w: None,
-                target_h: None,
-                target_alpha: 0.0,
-
-                registered_this_frame: false,
-                scroll_offset: 0.0,
-            }
-        }
-    }
-
-    thread_local! {
-        pub static HOVER_STATE: RefCell<HoverState> = RefCell::new(HoverState::new());
-        pub static CURSOR_POS: RefCell<(f32, f32)> = RefCell::new((0.0, 0.0));
-    }
-
-    pub fn set_cursor_pos(x: f32, y: f32) {
-        CURSOR_POS.with(|pos| {
-            *pos.borrow_mut() = (x, y);
-        });
-    }
-
-    pub fn reset_frame_registration() {
-        HOVER_STATE.with(|state| {
-            state.borrow_mut().registered_this_frame = false;
-        });
-    }
-
-    pub fn set_scroll_offset(offset: f32) {
-        HOVER_STATE.with(|state| {
-            state.borrow_mut().scroll_offset = offset;
-        });
-    }
-
-    pub fn get_scroll_offset() -> f32 {
-        HOVER_STATE.with(|state| {
-            state.borrow().scroll_offset
-        })
-    }
-
-    pub fn register_hovered(x: f32, y: f32, w: f32, h: f32, color: [f32; 4]) {
-        HOVER_STATE.with(|state| {
-            let mut s = state.borrow_mut();
-            s.target_x = Some(x);
-            s.target_y = Some(y);
-            s.target_w = Some(w);
-            s.target_h = Some(h);
-            s.target_alpha = color[3];
-            s.registered_this_frame = true;
-        });
-    }
-
-    pub fn post_render_check() {
-        HOVER_STATE.with(|state| {
-            let mut s = state.borrow_mut();
-            if !s.registered_this_frame {
-                s.target_alpha = 0.0;
-                let (cx, cy) = CURSOR_POS.with(|pos| *pos.borrow());
-                s.target_x = Some(cx);
-                s.target_y = Some(cy + s.scroll_offset);
-                s.target_w = Some(0.0);
-                s.target_h = Some(0.0);
-            }
-        });
-    }
-
-    pub fn tick(dt: f32) -> bool {
-        HOVER_STATE.with(|state| {
-            let mut s = state.borrow_mut();
-            let decay = 15.0;
-            let mut changed = false;
-
-            if s.current_alpha <= 0.001 && s.target_alpha > 0.0 {
-                if let (Some(tx), Some(ty), Some(tw), Some(th)) = (s.target_x, s.target_y, s.target_w, s.target_h) {
-                    s.current_x = tx;
-                    s.current_y = ty;
-                    s.current_w = tw;
-                    s.current_h = th;
-                }
-            }
-
-            if (s.current_alpha - s.target_alpha).abs() > 0.001 {
-                s.current_alpha += (s.target_alpha - s.current_alpha) * (1.0 - (-decay * dt).exp());
-                changed = true;
-            } else if s.current_alpha != s.target_alpha {
-                s.current_alpha = s.target_alpha;
-                changed = true;
-            }
-
-            if let (Some(tx), Some(ty), Some(tw), Some(th)) = (s.target_x, s.target_y, s.target_w, s.target_h) {
-                if (s.current_x - tx).abs() > 0.1 {
-                    s.current_x += (tx - s.current_x) * (1.0 - (-decay * dt).exp());
-                    changed = true;
-                } else if s.current_x != tx {
-                    s.current_x = tx;
-                    changed = true;
-                }
-
-                if (s.current_y - ty).abs() > 0.1 {
-                    s.current_y += (ty - s.current_y) * (1.0 - (-decay * dt).exp());
-                    changed = true;
-                } else if s.current_y != ty {
-                    s.current_y = ty;
-                    changed = true;
-                }
-
-                if (s.current_w - tw).abs() > 0.1 {
-                    s.current_w += (tw - s.current_w) * (1.0 - (-decay * dt).exp());
-                    changed = true;
-                } else if s.current_w != tw {
-                    s.current_w = tw;
-                    changed = true;
-                }
-
-                if (s.current_h - th).abs() > 0.1 {
-                    s.current_h += (th - s.current_h) * (1.0 - (-decay * dt).exp());
-                    changed = true;
-                } else if s.current_h != th {
-                    s.current_h = th;
-                    changed = true;
-                }
-            }
-
-            changed
-        })
-    }
-
-    pub fn get_quad() -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        HOVER_STATE.with(|state| {
-            let s = state.borrow();
-            if s.current_alpha > 0.001 {
-                Some((
-                    s.current_x,
-                    s.current_y,
-                    s.current_w,
-                    s.current_h,
-                    [1.0, 1.0, 1.0, s.current_alpha],
-                ))
-            } else {
-                None
-            }
-        })
-    }
-}
-
-pub mod clipboard {
-    pub fn copy_to_clipboard(text: &str) {
-        let text = text.to_string();
-        std::thread::spawn(move || {
-            if let Ok(mut child) = std::process::Command::new("wl-copy")
-                .stdin(std::process::Stdio::piped())
-                .spawn()
-            {
-                if let Some(mut stdin) = child.stdin.take() {
-                    use std::io::Write;
-                    let _ = stdin.write_all(text.as_bytes());
-                }
-                let _ = child.wait();
-            } else if let Ok(mut child) = std::process::Command::new("xclip")
-                .arg("-selection")
-                .arg("clipboard")
-                .stdin(std::process::Stdio::piped())
-                .spawn()
-            {
-                if let Some(mut stdin) = child.stdin.take() {
-                    use std::io::Write;
-                    let _ = stdin.write_all(text.as_bytes());
-                }
-                let _ = child.wait();
-            }
-        });
-    }
-
-    pub fn read_from_clipboard() -> Option<String> {
-        if let Ok(output) = std::process::Command::new("wl-paste")
-            .arg("-n")
-            .output()
-        {
-            if output.status.success() {
-                if let Ok(text) = String::from_utf8(output.stdout) {
-                    return Some(text);
-                }
-            }
-        }
-        if let Ok(output) = std::process::Command::new("xclip")
-            .arg("-selection")
-            .arg("clipboard")
-            .arg("-o")
-            .output()
-        {
-            if output.status.success() {
-                if let Ok(text) = String::from_utf8(output.stdout) {
-                    return Some(text);
-                }
-            }
-        }
-        None
-    }
-}
-
-pub mod context_menu {
-    use super::{Widget, TextBox, MouseButton, ElementState, TextLabel};
-    use std::cell::RefCell;
-
-    #[derive(Debug, Clone)]
-    pub struct ContextMenuState {
-        pub x: f32,
-        pub y: f32,
-        pub w: f32,
-        pub h: f32,
-        pub visible: bool,
-        pub options: Vec<String>,
-        pub hovered_item: Option<usize>,
-        pub target: Option<*mut TextBox>,
-    }
-
-    impl ContextMenuState {
-        pub fn new() -> Self {
-            Self {
-                x: 0.0,
-                y: 0.0,
-                w: 120.0,
-                h: 0.0,
-                visible: false,
-                options: Vec::new(),
-                hovered_item: None,
-                target: None,
-            }
-        }
-
-        pub fn show(&mut self, x: f32, y: f32, options: Vec<String>, target: *mut TextBox) {
-            self.x = x;
-            self.y = y;
-            self.options = options;
-            self.h = self.options.len() as f32 * 24.0;
-            self.visible = true;
-            self.hovered_item = None;
-            self.target = Some(target);
-        }
-
-        pub fn hide(&mut self) {
-            self.visible = false;
-            self.target = None;
-        }
-
-        pub fn hit_test(&self, px: f32, py: f32) -> bool {
-            if !self.visible { return false; }
-            px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h
-        }
-
-        pub fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
-            if !self.visible { return false; }
-            let was_hovered = self.hovered_item;
-            self.hovered_item = None;
-            if px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h {
-                let idx = ((py - self.y) / 24.0) as usize;
-                if idx < self.options.len() {
-                    self.hovered_item = Some(idx);
-                }
-            }
-            self.hovered_item != was_hovered
-        }
-
-        pub fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-            if !self.visible { return false; }
-            if button != MouseButton::Left || state != ElementState::Pressed {
-                if state == ElementState::Pressed {
-                    self.hide();
-                    return true;
-                }
-                return false;
-            }
-
-            if px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h {
-                let idx = ((py - self.y) / 24.0) as usize;
-                if idx < self.options.len() {
-                    let opt = self.options[idx].clone();
-                    if let Some(target_ptr) = self.target {
-                        unsafe {
-                            let target = &mut *target_ptr;
-                            match opt.as_str() {
-                                "Cut" => {
-                                    if target.cut_selection() {
-                                        target.just_changed = true;
-                                    }
-                                }
-                                "Copy" => {
-                                    target.copy_selection();
-                                }
-                                "Paste" => {
-                                    if target.paste_from_clipboard() {
-                                        target.just_changed = true;
-                                    }
-                                }
-                                "Select All" => {
-                                    target.select_all();
-                                }
-                                _ => {}
-                            }
-                        }
-                    }
-                }
-                self.hide();
-                return true;
-            } else {
-                self.hide();
-                return true;
-            }
-        }
-
-        pub fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-            let mut quads = Vec::new();
-            if !self.visible { return quads; }
-
-            // border
-            quads.push((self.x, self.y, self.w, self.h, [0.22, 0.22, 0.28, 1.0]));
-            // bg
-            quads.push((self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, [0.06, 0.06, 0.09, 1.0]));
-
-            if let Some(h_idx) = self.hovered_item {
-                let iy = self.y + h_idx as f32 * 24.0;
-                quads.push((self.x + 2.0, iy + 2.0, self.w - 4.0, 20.0, [0.20, 0.40, 0.65, 0.6]));
-            }
-
-            quads
-        }
-
-        pub fn text_labels(&self) -> Vec<TextLabel> {
-            let mut labels = Vec::new();
-            if !self.visible { return labels; }
-
-            for (idx, opt) in self.options.iter().enumerate() {
-                let iy = self.y + idx as f32 * 24.0 + (24.0 - 12.0) / 2.0;
-                let text_color = if self.hovered_item == Some(idx) {
-                    [0xff, 0xff, 0xff]
-                } else {
-                    [0xcc, 0xcc, 0xd4]
-                };
-
-                labels.push(TextLabel {
-                    text: opt.clone(),
-                    x: self.x + 8.0,
-                    y: iy,
-                    font_size: 12.0,
-                    color: text_color,
-                });
-            }
-            labels
-        }
-    }
-
-    thread_local! {
-        pub static CONTEXT_MENU: RefCell<ContextMenuState> = RefCell::new(ContextMenuState::new());
-    }
-
-    pub fn is_visible() -> bool {
-        CONTEXT_MENU.with(|m| m.borrow().visible)
-    }
-
-    pub fn show(x: f32, y: f32, options: Vec<String>, target: *mut TextBox) {
-        CONTEXT_MENU.with(|m| m.borrow_mut().show(x, y, options, target));
-    }
-
-    pub fn hide() {
-        CONTEXT_MENU.with(|m| m.borrow_mut().hide());
-    }
-
-    pub fn x() -> f32 { CONTEXT_MENU.with(|m| m.borrow().x) }
-    pub fn y() -> f32 { CONTEXT_MENU.with(|m| m.borrow().y) }
-    pub fn w() -> f32 { CONTEXT_MENU.with(|m| m.borrow().w) }
-    pub fn h() -> f32 { CONTEXT_MENU.with(|m| m.borrow().h) }
-    pub fn hovered_item() -> Option<usize> { CONTEXT_MENU.with(|m| m.borrow().hovered_item) }
-    pub fn options() -> Vec<String> { CONTEXT_MENU.with(|m| m.borrow().options.clone()) }
-
-    pub fn hit_test(px: f32, py: f32) -> bool {
-        CONTEXT_MENU.with(|m| m.borrow().hit_test(px, py))
-    }
-
-    pub fn cursor_moved(px: f32, py: f32) -> bool {
-        CONTEXT_MENU.with(|m| m.borrow_mut().cursor_moved(px, py))
-    }
-
-    pub fn mouse_input(button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        CONTEXT_MENU.with(|m| m.borrow_mut().mouse_input(button, state, px, py))
-    }
-
-    pub fn extra_quads() -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        CONTEXT_MENU.with(|m| m.borrow().extra_quads())
-    }
-
-    pub fn text_labels() -> Vec<TextLabel> {
-        CONTEXT_MENU.with(|m| m.borrow().text_labels())
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct TextLabel {
-    pub text: String,
-    pub x: f32,
-    pub y: f32,
-    pub font_size: f32,
-    pub color: [u8; 3],
-}
-
-impl TextLabel {
-    pub fn estimate_width(text: &str, font_size: f32) -> f32 {
-        let mut weight_sum = 0.0;
-        for c in text.chars() {
-            weight_sum += match c {
-                'i' | 'l' | 't' | 'j' | 'I' | ' ' | '.' | ',' | '!' | ';' | ':' | '\'' | '1' | '-' | '(' | ')' | '[' | ']' => 0.26,
-                'f' | 'r' | 's' | 'J' => 0.35,
-                'w' | 'm' | 'M' | 'W' => 0.72,
-                'A' | 'B' | 'C' | 'D' | 'E' | 'G' | 'H' | 'K' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'X' | 'Y' | 'Z' => 0.65,
-                _ => 0.52,
-            };
-        }
-        weight_sum * font_size
-    }
-
-    pub fn curved_layout(
-        text: &str,
-        cx: f32,
-        cy: f32,
-        r: f32,
-        start_angle: f32,
-        end_angle: f32,
-        font_size: f32,
-        color: [u8; 3],
-    ) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        let char_widths: Vec<f32> = text.chars().map(|c| {
-            Self::estimate_width(&c.to_string(), font_size)
-        }).collect();
-        let total_width: f32 = char_widths.iter().sum();
-        
-        let mid_angle = (start_angle + end_angle) / 2.0;
-        let angular_width = total_width / r;
-        let text_start_angle = mid_angle - angular_width / 2.0;
-        
-        let mut current_angle = text_start_angle;
-        for (i, c) in text.chars().enumerate() {
-            let cw = char_widths[i];
-            let dtheta = cw / r;
-            let char_center_angle = current_angle + dtheta / 2.0;
-            
-            let x = cx + r * char_center_angle.cos() - cw / 2.0;
-            let y = cy + r * char_center_angle.sin() - font_size / 2.0;
-            
-            labels.push(TextLabel {
-                text: c.to_string(),
-                x,
-                y,
-                font_size,
-                color,
-            });
-            
-            current_angle += dtheta;
-        }
-        labels
-    }
-
-    pub fn is_covered_by(&self, px: f32, py: f32, pw: f32, ph: f32) -> bool {
-        let text_w = Self::estimate_width(&self.text, self.font_size);
-        let x_overlap = self.x <= px + pw && (self.x + text_w) >= px;
-        let y_overlap = self.y <= py + ph && (self.y + self.font_size) >= py;
-        x_overlap && y_overlap
-    }
-}
-
-
-#[derive(Debug, Clone)]
-pub struct WidgetBase {
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-    pub label: Option<String>,
-    pub hovered: bool,
-    pub row_x: f32,
-    pub row_w: f32,
-    pub focused: bool,
-}
-
-impl WidgetBase {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            label: None,
-            hovered: false,
-            row_x: 0.0,
-            row_w: 0.0,
-            focused: false,
-        }
-    }
-
-    pub fn new_rect(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            x,
-            y,
-            w,
-            h,
-            label: None,
-            hovered: false,
-            row_x: 0.0,
-            row_w: 0.0,
-            focused: false,
-        }
-    }
-}
-
-#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
-pub struct GraphNode {
-    pub name: String,
-    pub position: (f32, f32), // (column, row)
-    pub parameters: Vec<(String, String, String)>, // (name, value, type)
-    pub geom_visible: bool,
-}
-
-pub trait Widget {
-    fn base(&self) -> Option<&WidgetBase> { None }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { None }
-
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        if let Some(b) = self.base() {
-            (b.x, b.y, b.w, b.h)
-        } else {
-            (0.0, 0.0, 0.0, 0.0)
-        }
-    }
-
-    fn label(&self) -> Option<String> { None }
-    
-    fn set_curved_circle(&mut self, _circle: Option<(f32, f32, f32)>) {}
-    fn set_uniform_background(&mut self, _uniform: bool) {}
-    fn set_network_opacity(&mut self, _opacity: f32) {}
-    fn set_cell_color(&mut self, _color: [f32; 3]) {}
-    fn set_gap_color(&mut self, _color: [f32; 3]) {}
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        if let Some(b) = self.base_mut() {
-            b.x = x;
-            b.y = y;
-            b.w = w;
-            b.h = h;
-        }
-    }
-
-    fn set_row_rect(&mut self, x: f32, w: f32) {
-        if let Some(b) = self.base_mut() {
-            b.row_x = x;
-            b.row_w = w;
-        }
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        let (x, y, w, h) = self.rect();
-        let (hx, hw) = if let Some(b) = self.base() {
-            if b.row_w > 0.0 {
-                (b.row_x, b.row_w)
-            } else {
-                (x, w)
-            }
-        } else {
-            (x, w)
-        };
-        let top = self.top_room();
-        let hy = y - top;
-        let hh = h + top;
-        px >= hx && px <= hx + hw && py >= hy && py <= hy + hh
-    }
-
-    fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        hover_animation::set_cursor_pos(px, py);
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            let was = self.hovered();
-            self.set_hovered(false);
-            return was;
-        }
-        self.on_cursor_moved(px, py)
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        if self.base().is_some() {
-            let was = self.hovered();
-            let is_hit = self.hit_test(px, py);
-            self.set_hovered(is_hit);
-            was != is_hit
-        } else {
-            false
-        }
-    }
-
-    fn mouse_input(&mut self, _button: MouseButton, _state: ElementState, _px: f32, _py: f32) -> bool { false }
-    fn mouse_wheel(&mut self, _delta: &MouseScrollDelta, _px: f32, _py: f32) -> bool { false }
-
-    fn set_hovered(&mut self, hovered: bool) {
-        if let Some(b) = self.base_mut() {
-            b.hovered = hovered;
-        }
-    }
-
-    fn hovered(&self) -> bool {
-        if let Some(b) = self.base() {
-            b.hovered
-        } else {
-            false
-        }
-    }
-
-    fn is_active(&self) -> bool { false }
-
-    fn highlight_color(&self) -> Option<[f32; 4]> {
-        let is_focused = self.base().map_or(false, |b| b.focused);
-        if is_focused || self.is_active() {
-            Some(colors::highlight_primary_color())
-        } else if self.hovered() {
-            Some(colors::HIGHLIGHT_SECONDARY)
-        } else {
-            None
-        }
-    }
-
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        let hc = self.highlight_color()?;
-        if let Some(b) = self.base() {
-            let hx = if b.row_w > 0.0 { b.row_x } else { b.x };
-            let hw = if b.row_w > 0.0 { b.row_w } else { b.w };
-            let top_offset = self.top_room();
-            let hy = b.y - top_offset;
-            let hh = b.h + top_offset;
-            Some((hx, hy, hw, hh, hc))
-        } else {
-            let (x, y, w, h) = self.rect();
-            let top_offset = self.top_room();
-            Some((x, y - top_offset, w, h + top_offset, hc))
-        }
-    }
-
-    fn color(&self) -> [f32; 4];
-
-    fn is_dragging(&self) -> bool { false }
-    fn drag_update(&mut self, _px: f32, _py: f32) -> bool { false }
-    fn drag_begin(&mut self, _px: f32, _py: f32) {}
-    fn drag_end(&mut self) {}
-    fn take_click(&mut self) -> bool { false }
-    fn draggable(&self) -> bool { false }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> { Vec::new() }
-    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> { Vec::new() }
-    fn all_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = self.extra_quads();
-        if let Some(hq) = self.highlight_quad() {
-            if hq.4 == colors::HIGHLIGHT_SECONDARY {
-                hover_animation::register_hovered(hq.0, hq.1, hq.2, hq.3, hq.4);
-            } else {
-                quads.push(hq);
-            }
-        }
-        quads
-    }
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if let Some(b) = self.base() {
-            if let Some(ref label) = b.label {
-                return vec![TextLabel {
-                    text: label.clone(),
-                    x: b.x,
-                    y: b.y - 18.0,
-                    font_size: 12.0,
-                    color: [0x83, 0x83, 0x8a],
-                }];
-            }
-        }
-        Vec::new()
-    }
-    fn widget_font(&self) -> Option<String> { None }
-    fn value(&self) -> i32 { 0 }
-    fn type_name(&self) -> &'static str {
-        let full_name = std::any::type_name::<Self>();
-        full_name.split("::").last().unwrap_or("Widget")
-    }
-    fn top_room(&self) -> f32 {
-        if let Some(b) = self.base() {
-            if b.label.is_some() {
-                return 18.0;
-            }
-        }
-        0.0
-    }
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> { None }
-    fn render_popover(&self, _pc: &mut dyn crate::layout::RenderTarget) {}
-    fn set_text(&mut self, text: &str) {
-        if let Some(b) = self.base_mut() {
-            b.label = Some(text.to_string());
-        }
-    }
-
-    fn set_drag_bounds(&mut self, _bx: f32, _by: f32, _bw: f32, _bh: f32) {}
-
-    fn focus(&mut self) {
-        if let Some(b) = self.base_mut() {
-            b.focused = true;
-        }
-    }
-    fn unfocus(&mut self) {
-        if let Some(b) = self.base_mut() {
-            b.focused = false;
-        }
-    }
-    fn focused(&self) -> bool {
-        if let Some(b) = self.base() {
-            b.focused
-        } else {
-            false
-        }
-    }
-    fn prepare_text(&mut self, _fs: &mut glyphon::FontSystem) {}
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> { Vec::new() }
-    fn set_selected(&mut self, _selected: bool) {}
-    fn keyboard_input(&mut self, _event: &KeyEvent) -> bool { false }
-
-    fn menu_click(&mut self) -> Option<(usize, usize)> { None }
-    fn get_menu_items_at(&self, _px: f32, _py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> { None }
-    fn trigger_menu_click(&mut self, _menu_idx: usize, _item_idx: usize) {}
-    fn set_item_checked(&mut self, _menu_idx: usize, _item_idx: usize, _checked: bool) {}
-    fn set_menu_items(&mut self, _menu_idx: usize, _items: &[String]) {}
-    fn is_menu_bar(&self) -> bool { false }
-    fn is_menu_open(&self) -> bool { false }
-    fn set_grid_snap(&mut self, _gx: f32, _gy: f32) {}
-    fn set_node_name(&mut self, _name: &str) {}
-    fn set_visible(&mut self, _visible: bool) {}
-    fn visible(&self) -> bool { true }
-    fn set_path(&mut self, _segments: &[String]) {}
-    fn path_click(&mut self) -> Option<usize> { None }
-
-    fn node_params(&self) -> Vec<(String, String, String)> { vec![] }
-    fn set_display_params(&mut self, _params: &[(String, String, String)]) {}
-
-    fn set_config_toggle(&mut self, _id: usize, _val: bool) {}
-    fn take_config_toggle(&mut self) -> Option<(usize, bool)> { None }
-    fn set_show_network_grid(&mut self, _show: bool) {}
-    fn set_config_spin(&mut self, _id: usize, _val: f32) {}
-    fn take_config_spin(&mut self) -> Option<(usize, f32)> { None }
-    fn set_grid_sizes(&mut self, _gx: f32, _gy: f32) {}
-    fn set_grid_origin(&mut self, _ox: f32, _oy: f32) {}
-    fn grid_origin(&self) -> (f32, f32) { (0.0, 0.0) }
-    fn set_skipped_sizes(&mut self, _row_h: f32, _col_w: f32) {}
-    fn set_palette_state(&mut self, _visible: bool, _query: &str, _items: &[String], _selected: usize) {}
-
-    fn set_geom_visible(&mut self, _visible: bool) {}
-    fn geom_visible(&self) -> bool { true }
-    fn take_geom_toggle(&mut self) -> bool { false }
-    fn set_spreadsheet_data(&mut self, _headers: Vec<String>, _rows: Vec<Vec<String>>) {}
-    fn tick(&mut self, _dt: f32) -> bool { false }
-
-    fn set_nodes(&mut self, _nodes: &[GraphNode]) {}
-    fn get_nodes(&self) -> Vec<GraphNode> { vec![] }
-    fn selected_node(&self) -> Option<usize> { None }
-    fn set_selected_node(&mut self, _idx: Option<usize>) {}
-    fn double_clicked_node(&self) -> Option<usize> { None }
-    fn clear_double_clicked_node(&mut self) {}
-    fn set_grid_snap_enabled(&mut self, _enabled: bool) {}
-    fn take_node_geom_toggle(&mut self) -> Option<(usize, bool)> { None }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { None }
-    fn set_parent(&mut self, _parent: Option<*mut (dyn Widget + 'static)>) {}
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { vec![] }
-    fn add_child(&mut self, _child: *mut (dyn Widget + 'static)) {}
-    fn clear_children(&mut self) {}
-    fn z_index(&self) -> i32 { 0 }
-    fn set_center_items(&mut self, _center: bool) {}
-    fn menu_items(&self) -> Vec<String> { vec![] }
-    fn menu_item_checked(&self) -> Vec<Option<bool>> { vec![] }
-    fn is_vertical(&self) -> bool { false }
-
-    fn is_plate(&self) -> bool { false }
-    fn rounded_corners(&self) -> (bool, bool, bool, bool) { (false, false, false, false) }
-    fn layout_ignore(&self) -> bool { false }
-    fn text_labels_with_bounds(&self) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        self.text_labels().into_iter().map(|l| (l, None)).collect()
-    }
-    fn text_labels_with_font_and_bounds(&self) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
-        self.text_labels().into_iter().map(|l| (l, None, None)).collect()
-    }
-
-    fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
-    fn menu_names(&self) -> Vec<String> { vec![] }
-    fn selected_page(&self) -> usize { 0 }
-    fn set_selected_page(&mut self, _page: usize) {}
-    fn is_page_hidden(&self) -> bool { false }
-    fn set_page_hidden(&mut self, _hidden: bool) {}
-    fn set_pages(&mut self, _pages: Vec<String>) {}
-    fn sidebar_w(&self) -> f32 { 0.0 }
-    fn set_sidebar_mode(&mut self, _enabled: bool) {}
-    fn set_sidebar_label(&mut self, _label: Option<String>) {}
-    fn add_widget_to_page(&mut self, _page_idx: usize, _widget: *mut (dyn Widget + 'static)) {}
-    fn clear_page_widgets(&mut self, _page_idx: usize) {}
-    fn menu_items_list(&self) -> Vec<Vec<String>> { vec![] }
-    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> { vec![] }
-    fn color_u8(&self) -> Option<[u8; 4]> { None }
-    fn update_bounds(&mut self, _count: usize, _viewport_y: f32, _viewport_h: f32) {}
-    fn get_item_draw_y(&self, _idx: usize, _offset: f32) -> Option<f32> { None }
-}
-
-#[derive(Clone)]
-pub struct Container {
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-}
-
-impl Container {
-    pub fn new() -> Self {
-        Self { parent: None, children: Vec::new() }
-    }
-}
-
-impl Widget for Container {
-    fn rect(&self) -> (f32, f32, f32, f32) { (0.0, 0.0, 0.0, 0.0) }
-    fn set_rect(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-
-    fn focus(&mut self) {
-        focus::set_focused(self);
-    }
-    fn unfocus(&mut self) {}
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-}
-
-impl Drop for Container {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-
-pub struct Header {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
-
-impl Header {
-    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
-}
-
-impl Widget for Header {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { colors::HEADER_BG }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-}
-
-pub struct ContentBg {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    show_network_grid: bool,
-    grid_size_x: f32,
-    grid_size_y: f32,
-    grid_origin_x: f32,
-    grid_origin_y: f32,
-    skipped_row_h: f32,
-    skipped_col_w: f32,
-}
-
-impl ContentBg {
-    pub fn new() -> Self {
-        Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false, show_network_grid: false, grid_size_x: 150.0, grid_size_y: 75.0, grid_origin_x: 0.0, grid_origin_y: 0.0, skipped_row_h: 37.5, skipped_col_w: 37.5 }
-    }
-}
-
-impl Widget for ContentBg {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] {
-        if self.show_network_grid {
-            [0.0, 0.0, 0.0, 0.0]
-        } else {
-            colors::CONTENT_BG
-        }
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, _px: f32, _py: f32) -> bool { false }
-
-    fn set_show_network_grid(&mut self, show: bool) { self.show_network_grid = show; }
-    fn set_grid_sizes(&mut self, gx: f32, gy: f32) { self.grid_size_x = gx; self.grid_size_y = gy; }
-    fn set_grid_origin(&mut self, ox: f32, oy: f32) { self.grid_origin_x = ox; self.grid_origin_y = oy; }
-    fn set_skipped_sizes(&mut self, row_h: f32, col_w: f32) { self.skipped_row_h = row_h; self.skipped_col_w = col_w; }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.show_network_grid || self.grid_size_x <= 0.0 || self.grid_size_y <= 0.0 {
-            return vec![];
-        }
-        let mut quads = Vec::new();
-        let grid_color = [0.0, 0.0, 0.0, 0.0];
-        let max_alpha = colors::CONTENT_BG[3]; // Peak opacity in the middle of gradient cells matches non-gradient cells
-        let steps = 20; // Silky-smooth gradient transition
-
-        let step_y = self.grid_size_y + self.skipped_row_h;
-        let step_x = self.grid_size_x + self.skipped_col_w;
-
-        if step_y >= 4.0 && step_x >= 4.0 {
-            let ry_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
-            let ry_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
-            let ry_start = ry_start.max(-100_000);
-            let ry_end = ry_end.min(100_000);
-
-            let cx_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
-            let cx_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
-            let cx_start = cx_start.max(-100_000);
-            let cx_end = cx_end.min(100_000);
-
-            // Draw individual cell backgrounds to avoid stacking with gradients
-            for ry in ry_start..=ry_end {
-                let y1 = self.grid_origin_y + (ry as f32) * step_y;
-                let draw_start_y = y1.max(self.y);
-                let draw_end_y = (y1 + self.grid_size_y).min(self.y + self.h);
-                if draw_start_y < draw_end_y {
-                    for cx in cx_start..=cx_end {
-                        let x1 = self.grid_origin_x + (cx as f32) * step_x;
-                        let draw_start_x = x1.max(self.x);
-                        let draw_end_x = (x1 + self.grid_size_x).min(self.x + self.w);
-                        if draw_start_x < draw_end_x {
-                            quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, colors::CONTENT_BG));
-                        }
-                    }
-                }
-            }
-        }
-
-        // Draw interstitial row gradients (horizontal bands fading to 0 alpha at left and right sides)
-        if self.skipped_row_h > 0.0 {
-            let step_y = self.grid_size_y + self.skipped_row_h;
-            let step_x = self.grid_size_x + self.skipped_col_w;
-            if step_y >= 4.0 && step_x >= 4.0 {
-                let k_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
-                let k_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
-                let k_start = k_start.max(-100_000);
-                let k_end = k_end.min(100_000);
-
-                let cx_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
-                let cx_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
-                let cx_start = cx_start.max(-100_000);
-                let cx_end = cx_end.min(100_000);
-
-                for k in k_start..=k_end {
-                    let y1 = self.grid_origin_y + (k as f32) * step_y;
-                    let y2 = y1 + self.grid_size_y;
-                    if y1 >= self.y + self.h {
-                        continue;
-                    }
-                    let draw_start_y = y2.max(self.y);
-                    let draw_end_y = (y2 + self.skipped_row_h).min(self.y + self.h);
-                    if draw_start_y >= draw_end_y {
-                        continue;
-                    }
-
-                    for cx in cx_start..=cx_end {
-                        let x1 = self.grid_origin_x + (cx as f32) * step_x;
-                        let x_mid = x1 + self.grid_size_x / 2.0;
-                        let w_total = self.grid_size_x;
-                        let sub_w = w_total / steps as f32;
-
-                        for i in 0..steps {
-                            let sx_start = x1 + i as f32 * sub_w;
-                            let sx_end = sx_start + sub_w;
-                            let draw_start_x = sx_start.max(self.x);
-                            let draw_end_x = sx_end.min(self.x + self.w);
-                            if draw_start_x < draw_end_x {
-                                let sx_mid = (sx_start + sx_end) / 2.0;
-                                let dist = (sx_mid - x_mid).abs();
-                                let d = (dist / (w_total / 2.0)).min(1.0);
-                                
-                                // Fade the cell background color from max_alpha in the middle to transparent at the edges
-                                let alpha = max_alpha * (1.0 - d);
-                                if alpha > 0.001 {
-                                    quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Draw interstitial column gradients (vertical bands fading to 0 alpha at top and bottom)
-        if self.skipped_col_w > 0.0 {
-            let step_y = self.grid_size_y + self.skipped_row_h;
-            let step_x = self.grid_size_x + self.skipped_col_w;
-            if step_y >= 4.0 && step_x >= 4.0 {
-                let k_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
-                let k_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
-                let k_start = k_start.max(-100_000);
-                let k_end = k_end.min(100_000);
-
-                let ry_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
-                let ry_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
-                let ry_start = ry_start.max(-100_000);
-                let ry_end = ry_end.min(100_000);
-
-                for k in k_start..=k_end {
-                    let x1 = self.grid_origin_x + (k as f32) * step_x;
-                    let x2 = x1 + self.grid_size_x;
-                    if x1 >= self.x + self.w {
-                        continue;
-                    }
-                    let draw_start_x = x2.max(self.x);
-                    let draw_end_x = (x2 + self.skipped_col_w).min(self.x + self.w);
-                    if draw_start_x >= draw_end_x {
-                        continue;
-                    }
-
-                    for ry in ry_start..=ry_end {
-                        let y1 = self.grid_origin_y + (ry as f32) * step_y;
-                        let y_mid = y1 + self.grid_size_y / 2.0;
-                        let h_total = self.grid_size_y;
-                        let sub_h = h_total / steps as f32;
-
-                        for i in 0..steps {
-                            let sy_start = y1 + i as f32 * sub_h;
-                            let sy_end = sy_start + sub_h;
-                            let draw_start_y = sy_start.max(self.y);
-                            let draw_end_y = sy_end.min(self.y + self.h);
-                            if draw_start_y < draw_end_y {
-                                let sy_mid = (sy_start + sy_end) / 2.0;
-                                let dist = (sy_mid - y_mid).abs();
-                                let d = (dist / (h_total / 2.0)).min(1.0);
-                                
-                                // Fade the cell background color from max_alpha in the middle to transparent at the edges
-                                let alpha = max_alpha * (1.0 - d);
-                                if alpha > 0.001 {
-                                    quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Draw the grid borders
-        let step_y = self.grid_size_y + self.skipped_row_h;
-        if step_y >= 4.0 {
-            let k_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
-            let k_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
-            let k_start = k_start.max(-100_000);
-            let k_end = k_end.min(100_000);
-            for k in k_start..=k_end {
-                let y1 = self.grid_origin_y + (k as f32) * step_y;
-                let y2 = y1 + self.grid_size_y;
-                if y1 >= self.y + self.h {
-                    continue;
-                }
-                if y1 >= self.y {
-                    quads.push((self.x, y1, self.w, 1.0, grid_color));
-                }
-                if y2 >= self.y && y2 < self.y + self.h {
-                    quads.push((self.x, y2, self.w, 1.0, grid_color));
-                }
-            }
-        }
-
-        let step_x = self.grid_size_x + self.skipped_col_w;
-        if step_x >= 4.0 {
-            let k_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
-            let k_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
-            let k_start = k_start.max(-100_000);
-            let k_end = k_end.min(100_000);
-            for k in k_start..=k_end {
-                let x1 = self.grid_origin_x + (k as f32) * step_x;
-                let x2 = x1 + self.grid_size_x;
-                if x1 >= self.x + self.w {
-                    continue;
-                }
-                if x1 >= self.x {
-                    quads.push((x1, self.y, 1.0, self.h, grid_color));
-                }
-                if x2 >= self.x && x2 < self.x + self.w {
-                    quads.push((x2, self.y, 1.0, self.h, grid_color));
-                }
-            }
-        }
-        quads
-    }
-}
-
-pub struct ViewportBg {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
-
-impl ViewportBg {
-    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
-}
-
-impl Widget for ViewportBg {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { colors::VIEWPORT_BG }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, _px: f32, _py: f32) -> bool { false }
-}
-
-pub struct ParametersBg {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    display_params: Vec<(String, String, String)>,
-    dragging_param: Option<usize>,
-    drag_offset: f32,
-    pub focused_param: Option<usize>,
-    mouse_pos: Option<(f32, f32)>,
-    sliders: Vec<Option<Slider>>,
-    float3s: Vec<Option<Float3>>,
-    spinboxes: Vec<Option<Spinbox>>,
-    visible: bool,
-}
-
-impl ParametersBg {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            hovered: false,
-            display_params: Vec::new(),
-            dragging_param: None,
-            drag_offset: 0.0,
-            focused_param: None,
-            mouse_pos: None,
-            sliders: Vec::new(),
-            float3s: Vec::new(),
-            spinboxes: Vec::new(),
-            visible: true,
-        }
-    }
-
-    pub fn get_param_rects(&self) -> Vec<(f32, f32, f32, f32)> {
-        let mut rects = Vec::new();
-        let mut cur_y = self.y + 30.0;
-        for p in &self.display_params {
-            let h = if p.2 == "code" {
-                200.0
-            } else if p.2 == "section" {
-                24.0
-            } else if p.2.starts_with("float3") {
-                108.0
-            } else if p.2 == "text" {
-                24.0
-            } else {
-                20.0
-            };
-            rects.push((self.x + 8.0, cur_y, self.w - 16.0, h));
-            cur_y += h + 8.0;
-        }
-        rects
-    }
-
-    fn update_slider_rects(&mut self) {
-        let rects = self.get_param_rects();
-        for (i, s_opt) in self.sliders.iter_mut().enumerate() {
-            if let Some(s) = s_opt {
-                let r = rects[i];
-                let track_x = self.x + 100.0;
-                let track_w = (self.w - 100.0 - 20.0).max(10.0);
-                let track_y = r.1 + 4.0;
-                let track_h = 12.0;
-                s.set_rect(track_x, track_y, track_w, track_h);
-            }
-        }
-        for (i, f_opt) in self.float3s.iter_mut().enumerate() {
-            if let Some(f) = f_opt {
-                let r = rects[i];
-                f.set_rect(r.0, r.1, r.2, r.3);
-            }
-        }
-        for (i, sb_opt) in self.spinboxes.iter_mut().enumerate() {
-            if let Some(sb) = sb_opt {
-                let r = rects[i];
-                let box_x = self.x + 100.0;
-                let box_w = (self.w - 100.0 - 16.0).max(10.0);
-                sb.set_rect(box_x, r.1, box_w, r.3);
-            }
-        }
-    }
-}
-
-fn parse_slider_range(ptype: &str) -> (f32, f32) {
-    if ptype.starts_with("slider:") || ptype.starts_with("float3:") {
-        let parts: Vec<&str> = ptype.split(':').collect();
-        if parts.len() >= 3 {
-            if let (Ok(min), Ok(max)) = (parts[1].parse::<f32>(), parts[2].parse::<f32>()) {
-                return (min, max);
-            }
-        }
-    }
-    (0.0, 2.0)
-}
-
-fn parse_spinbox_range(ptype: &str) -> (i32, i32, i32) {
-    if ptype.starts_with("spinbox:") {
-        let parts: Vec<&str> = ptype.split(':').collect();
-        if parts.len() >= 4 {
-            if let (Ok(min), Ok(max), Ok(step)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>(), parts[3].parse::<i32>()) {
-                return (min, max, step);
-            }
-        } else if parts.len() == 3 {
-            if let (Ok(min), Ok(max)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>()) {
-                return (min, max, 1);
-            }
-        }
-    }
-    (0, 10000, 1)
-}
-
-fn parse_float3_value(val_str: &str, min: f32, max: f32) -> [f32; 3] {
-    let mut out = [0.5, 0.5, 0.5];
-    let parts: Vec<&str> = val_str
-        .split(|c| c == ':' || c == ',' || c == ' ')
-        .filter(|s| !s.is_empty())
-        .collect();
-    for i in 0..3 {
-        if i < parts.len() {
-            if let Ok(v) = parts[i].parse::<f32>() {
-                let range = max - min;
-                if range != 0.0 {
-                    out[i] = ((v - min) / range).clamp(0.0, 1.0);
-                } else {
-                    out[i] = 0.0;
-                }
-            }
-        }
-    }
-    out
-}
-
-impl Widget for ParametersBg {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.x = x; self.y = y; self.w = w; self.h = h;
-        self.update_slider_rects();
-    }
-    fn color(&self) -> [f32; 4] {
-        if !self.visible {
-            return [0.0, 0.0, 0.0, 0.0];
-        }
-        colors::PARAM_BG
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-    }
-    fn visible(&self) -> bool {
-        self.visible
-    }
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h
-    }
-
-    fn set_display_params(&mut self, params: &[(String, String, String)]) {
-        let mut layout_changed = self.display_params.len() != params.len();
-        if !layout_changed {
-            for (p_old, p_new) in self.display_params.iter().zip(params.iter()) {
-                if p_old.0 != p_new.0 || p_old.2 != p_new.2 {
-                    layout_changed = true;
-                    break;
-                }
-            }
-        }
-
-        if layout_changed {
-            self.display_params = params.to_vec();
-            self.focused_param = None;
-            self.sliders = self.display_params.iter().map(|p| {
-                if p.2.starts_with("slider") {
-                    let val = p.1.parse::<f32>().unwrap_or(0.0);
-                    let (min, max) = parse_slider_range(&p.2);
-                    let t = if max - min != 0.0 {
-                        ((val - min) / (max - min)).clamp(0.0, 1.0)
-                    } else {
-                        0.0
-                    };
-                    Some(Slider::new().with_value(t).with_range(min, max).with_readout(true))
-                } else {
-                    None
-                }
-            }).collect();
-            self.float3s = self.display_params.iter().map(|p| {
-                if p.2.starts_with("float3") {
-                    let (min, max) = parse_slider_range(&p.2);
-                    let vals = parse_float3_value(&p.1, min, max);
-                    Some(Float3::new().with_values(vals).with_range(min, max).with_label(&p.0))
-                } else {
-                    None
-                }
-            }).collect();
-            self.spinboxes = self.display_params.iter().map(|p| {
-                if p.2.starts_with("spinbox") {
-                    let (min, max, step) = parse_spinbox_range(&p.2);
-                    let val = p.1.parse::<i32>().unwrap_or(min);
-                    Some(Spinbox::new(val, min, max, step))
-                } else {
-                    None
-                }
-            }).collect();
-        } else {
-            for (i, p_new) in params.iter().enumerate() {
-                if Some(i) != self.focused_param && Some(i) != self.dragging_param {
-                    self.display_params[i].1 = p_new.1.clone();
-                    if let Some(ref mut s) = self.sliders[i] {
-                        let val = p_new.1.parse::<f32>().unwrap_or(0.0);
-                        let (min, max) = parse_slider_range(&p_new.2);
-                        let t = if max - min != 0.0 {
-                            ((val - min) / (max - min)).clamp(0.0, 1.0)
-                        } else {
-                            0.0
-                        };
-                        s.set_value(t);
-                    } else if let Some(ref mut f) = self.float3s[i] {
-                        let (min, max) = parse_slider_range(&p_new.2);
-                        let vals = parse_float3_value(&p_new.1, min, max);
-                        f.set_values(vals);
-                    } else if let Some(ref mut sb) = self.spinboxes[i] {
-                        if !sb.editing {
-                            let (min, max, _step) = parse_spinbox_range(&p_new.2);
-                            let val = p_new.1.parse::<i32>().unwrap_or(min);
-                            sb.value = val;
-                        }
-                    }
-                }
-            }
-        }
-        self.update_slider_rects();
-    }
-
-    fn unfocus(&mut self) {
-        if let Some(idx) = self.focused_param {
-            if idx < self.display_params.len() {
-                let p = &mut self.display_params[idx];
-                if p.2.starts_with("spinbox") {
-                    if let Some(sb) = &mut self.spinboxes[idx] {
-                        sb.unfocus();
-                        p.1 = sb.value.to_string();
-                    }
-                } else if p.2.starts_with("slider") {
-                    if let Some(s) = &mut self.sliders[idx] {
-                        s.unfocus();
-                        let (min, max) = parse_slider_range(&p.2);
-                        let new_val = min + s.value * (max - min);
-                        p.1 = format!("{:.2}", new_val);
-                    }
-                } else if p.2.starts_with("float3") {
-                    if let Some(f) = &mut self.float3s[idx] {
-                        f.unfocus();
-                        let (min, max) = parse_slider_range(&p.2);
-                        let val0 = min + f.values[0] * (max - min);
-                        let val1 = min + f.values[1] * (max - min);
-                        let val2 = min + f.values[2] * (max - min);
-                        p.1 = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
-                    }
-                }
-            }
-        }
-        self.focused_param = None;
-    }
-
-    fn node_params(&self) -> Vec<(String, String, String)> {
-        self.display_params.clone()
-    }
-
-    fn draggable(&self) -> bool {
-        self.dragging_param.is_some() 
-            || self.display_params.iter().any(|p| p.2.starts_with("slider") || p.2.starts_with("float3"))
-    }
-
-    fn is_dragging(&self) -> bool {
-        self.dragging_param.is_some()
-    }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        let rects = self.get_param_rects();
-        for (i, p) in self.display_params.iter().enumerate() {
-            if p.2.starts_with("slider") {
-                let r = rects[i];
-                let row_y = r.1;
-                if py >= row_y - 2.0 && py <= row_y + 18.0 {
-                    if let Some(s) = &mut self.sliders[i] {
-                        s.drag_begin(px, py);
-                        self.dragging_param = Some(i);
-                        break;
-                    }
-                }
-            } else if p.2.starts_with("float3") {
-                let r = rects[i];
-                if py >= r.1 && py <= r.1 + r.3 {
-                    if let Some(f) = &mut self.float3s[i] {
-                        if f.mouse_input(MouseButton::Left, ElementState::Pressed, px, py) {
-                            self.dragging_param = Some(i);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        if let Some(i) = self.dragging_param {
-            if let Some(s) = &mut self.sliders[i] {
-                if s.drag_update(px, py) {
-                    let (min, max) = parse_slider_range(&self.display_params[i].2);
-                    let new_val = min + s.value * (max - min);
-                    let old_val = &self.display_params[i].1;
-                    let new_val_str = format!("{:.2}", new_val);
-                    if *old_val != new_val_str {
-                        self.display_params[i].1 = new_val_str;
-                        return true;
-                    }
-                }
-            } else if let Some(f) = &mut self.float3s[i] {
-                if f.drag_update(px, py) {
-                    let (min, max) = parse_slider_range(&self.display_params[i].2);
-                    let val0 = min + f.values[0] * (max - min);
-                    let val1 = min + f.values[1] * (max - min);
-                    let val2 = min + f.values[2] * (max - min);
-                    let new_val_str = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
-                    let old_val = &self.display_params[i].1;
-                    if *old_val != new_val_str {
-                        self.display_params[i].1 = new_val_str;
-                        return true;
-                    }
-                }
-            }
-        }
-        false
-    }
-
-    fn drag_end(&mut self) {
-        if let Some(i) = self.dragging_param.take() {
-            if let Some(s) = &mut self.sliders[i] {
-                s.drag_end();
-            } else if let Some(f) = &mut self.float3s[i] {
-                f.drag_end();
-            }
-        }
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        self.mouse_pos = Some((px, py));
-        true
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button == MouseButton::Left && state == ElementState::Pressed {
-            let rects = self.get_param_rects();
-            let mut clicked_any_focusable = false;
-            for (i, p) in self.display_params.iter_mut().enumerate() {
-                if p.2 == "code" {
-                    let r = rects[i];
-                    if px >= r.0 && px <= r.0 + r.2 && py >= r.1 + 18.0 && py <= r.1 + r.3 {
-                        self.focused_param = Some(i);
-                        clicked_any_focusable = true;
-                        break;
-                    }
-                } else if p.2 == "text" {
-                    let box_x = self.x + 100.0;
-                    let box_w = (self.w - 100.0 - 16.0).max(10.0);
-                    let r = rects[i];
-                    if px >= box_x && px <= box_x + box_w && py >= r.1 && py <= r.1 + r.3 {
-                        self.focused_param = Some(i);
-                        clicked_any_focusable = true;
-                        break;
-                    }
-                } else if p.2.starts_with("spinbox") {
-                    if let Some(sb) = &mut self.spinboxes[i] {
-                        if sb.mouse_input(button, state, px, py) {
-                            p.1 = sb.value.to_string();
-                            if sb.editing {
-                                self.focused_param = Some(i);
-                                clicked_any_focusable = true;
-                            } else {
-                                self.unfocus();
-                            }
-                            return true;
-                        }
-                    }
-                } else if p.2.starts_with("slider") {
-                    let r = rects[i];
-                    if py >= r.1 && py <= r.1 + r.3 {
-                        if let Some(s) = &mut self.sliders[i] {
-                            if s.mouse_input(button, state, px, py) {
-                                if s.editing {
-                                    self.focused_param = Some(i);
-                                    clicked_any_focusable = true;
-                                }
-                                break;
-                            }
-                        }
-                    }
-                } else if p.2.starts_with("float3") {
-                    let r = rects[i];
-                    if py >= r.1 && py <= r.1 + r.3 {
-                        if let Some(f) = &mut self.float3s[i] {
-                            if f.mouse_input(button, state, px, py) {
-                                if f.editing_idx.is_some() {
-                                    self.focused_param = Some(i);
-                                    clicked_any_focusable = true;
-                                }
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-            if !clicked_any_focusable {
-                self.unfocus();
-            }
-            return true;
-        }
-        false
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if let Some(idx) = self.focused_param {
-            if event.state == ElementState::Pressed {
-                let p = &mut self.display_params[idx];
-                if p.2 == "code" {
-                    match &event.logical_key {
-                        Key::Named(NamedKey::Backspace) => {
-                            if !p.1.is_empty() {
-                                p.1.pop();
-                                return true;
-                            }
-                        }
-                        Key::Named(NamedKey::Enter) => {
-                            p.1.push('\n');
-                            return true;
-                        }
-                        Key::Named(NamedKey::Escape) => {
-                            self.focused_param = None;
-                            return true;
-                        }
-                        Key::Character(s) => {
-                            p.1.push_str(s);
-                            return true;
-                        }
-                        _ => {}
-                    }
-                } else if p.2 == "text" {
-                    match &event.logical_key {
-                        Key::Named(NamedKey::Backspace) => {
-                            if !p.1.is_empty() {
-                                p.1.pop();
-                                return true;
-                            }
-                        }
-                        Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Escape) => {
-                            self.focused_param = None;
-                            return true;
-                        }
-                        Key::Character(s) => {
-                            p.1.push_str(s);
-                            return true;
-                        }
-                        _ => {}
-                    }
-                } else if p.2.starts_with("spinbox") {
-                    if let Some(sb) = &mut self.spinboxes[idx] {
-                        if sb.keyboard_input(event) {
-                            if !sb.editing {
-                                p.1 = sb.value.to_string();
-                                self.focused_param = None;
-                            } else {
-                                p.1 = sb.edit_buffer.clone();
-                            }
-                            return true;
-                        }
-                    }
-                } else if p.2.starts_with("slider") {
-                    if let Some(s) = &mut self.sliders[idx] {
-                        if s.keyboard_input(event) {
-                            let (min, max) = parse_slider_range(&p.2);
-                            let new_val = min + s.value * (max - min);
-                            p.1 = format!("{:.2}", new_val);
-                            if !s.editing {
-                                self.focused_param = None;
-                            }
-                            return true;
-                        }
-                    }
-                } else if p.2.starts_with("float3") {
-                    if let Some(f) = &mut self.float3s[idx] {
-                        if f.keyboard_input(event) {
-                            let (min, max) = parse_slider_range(&p.2);
-                            let val0 = min + f.values[0] * (max - min);
-                            let val1 = min + f.values[1] * (max - min);
-                            let val2 = min + f.values[2] * (max - min);
-                            p.1 = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
-                            if f.editing_idx.is_none() {
-                                self.focused_param = None;
-                            }
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        false
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        let mut changed = false;
-        let rects = self.get_param_rects();
-        for (i, p) in self.display_params.iter_mut().enumerate() {
-            if p.2.starts_with("slider") {
-                let r = rects[i];
-                let row_y = r.1;
-                if py >= row_y - 2.0 && py <= row_y + 18.0 && px >= self.x && px <= self.x + self.w {
-                    if let Some(s) = &mut self.sliders[i] {
-                        let was_scroll = s.scroll_enabled;
-                        s.set_scroll(true);
-                        if s.mouse_wheel(delta, px, py) {
-                            let (min, max) = parse_slider_range(&p.2);
-                            let new_val = min + s.value * (max - min);
-                            let old_val = &p.1;
-                            let new_val_str = format!("{:.2}", new_val);
-                            if *old_val != new_val_str {
-                                p.1 = new_val_str;
-                                changed = true;
-                            }
-                        }
-                        s.set_scroll(was_scroll);
-                    }
-                }
-            } else if p.2.starts_with("float3") {
-                let r = rects[i];
-                let row_y = r.1;
-                if py >= row_y && py <= row_y + r.3 && px >= self.x && px <= self.x + self.w {
-                    if let Some(f) = &mut self.float3s[i] {
-                        let rects_inner = f.get_row_rects();
-                        for j in 0..3 {
-                            let r_inner = rects_inner[j];
-                            if py >= r_inner.1 && py <= r_inner.1 + r_inner.3 {
-                                let scroll_amount = match delta {
-                                    MouseScrollDelta::LineDelta(_x, y) => *y,
-                                    MouseScrollDelta::PixelDelta(pos) => (pos.y as f32) / 120.0,
-                                };
-                                let step = 0.02;
-                                let new_val = (f.values[j] - scroll_amount * step).clamp(0.0, 1.0);
-                                if (new_val - f.values[j]).abs() > 0.0001 {
-                                    f.values[j] = new_val;
-                                    if f.editing_idx == Some(j) {
-                                        let scaled_val = f.mins[j] + f.values[j] * (f.maxs[j] - f.mins[j]);
-                                        f.edit_buffer = format!("{:.2}", scaled_val);
-                                    }
-                                    let (min, max) = parse_slider_range(&p.2);
-                                    let val0 = min + f.values[0] * (max - min);
-                                    let val1 = min + f.values[1] * (max - min);
-                                    let val2 = min + f.values[2] * (max - min);
-                                    let new_val_str = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
-                                    if p.1 != new_val_str {
-                                        p.1 = new_val_str;
-                                        changed = true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            } else if p.2.starts_with("spinbox") {
-                let r = rects[i];
-                let row_y = r.1;
-                if py >= row_y && py <= row_y + r.3 && px >= self.x && px <= self.x + self.w {
-                    if let Some(sb) = &mut self.spinboxes[i] {
-                        let scroll_amount = match delta {
-                            MouseScrollDelta::LineDelta(_x, y) => *y as i32,
-                            MouseScrollDelta::PixelDelta(pos) => {
-                                let dy = pos.y;
-                                if dy > 0.0 { 1 } else if dy < 0.0 { -1 } else { 0 }
-                            }
-                        };
-                        let new_val = (sb.value + scroll_amount * sb.step).clamp(sb.min, sb.max);
-                        if sb.value != new_val {
-                            sb.value = new_val;
-                            p.1 = new_val.to_string();
-                            changed = true;
-                        }
-                    }
-                }
-            }
-        }
-        changed
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        let rects = self.get_param_rects();
-
-        // Find sections and their ranges
-        let mut sections = Vec::new();
-        let mut current_section: Option<(usize, usize)> = None;
-        let mut in_section = false;
-        for (i, p) in self.display_params.iter().enumerate() {
-            if p.2 == "section" {
-                if let Some((start, end)) = current_section {
-                    sections.push((start, end));
-                }
-                current_section = None;
-                in_section = true;
-            } else {
-                if in_section {
-                    if let Some((start, ref mut end)) = current_section {
-                        *end = i;
-                    } else {
-                        current_section = Some((i, i));
-                    }
-                }
-            }
-        }
-        if let Some((start, end)) = current_section {
-            sections.push((start, end));
-        }
-
-        // Draw section border boxes
-        for (start, end) in sections {
-            if start <= end && start < rects.len() && end < rects.len() {
-                let r_start = rects[start];
-                let r_end = rects[end];
-                let bx = self.x + 4.0;
-                let bw = self.w - 8.0;
-                let by = r_start.1 - 4.0;
-                let bh = (r_end.1 + r_end.3 + 4.0) - by;
-                
-                let border_color = [0.18, 0.18, 0.27, 1.0];
-                let border_t = 1.0;
-                
-                // Top border
-                quads.push((bx, by, bw, border_t, border_color));
-                // Bottom border
-                quads.push((bx, by + bh - border_t, bw, border_t, border_color));
-                // Left border
-                quads.push((bx, by, border_t, bh, border_color));
-                // Right border
-                quads.push((bx + bw - border_t, by, border_t, bh, border_color));
-            }
-        }
-
-        for (i, p) in self.display_params.iter().enumerate() {
-            let r = rects[i];
-            if p.2.starts_with("slider") {
-                if let Some(s) = &self.sliders[i] {
-                    let (sx, sy, sw, sh) = s.rect();
-                    quads.push((sx, sy, sw, sh, s.color()));
-                    quads.extend(s.extra_quads());
-                }
-            } else if p.2 == "section" {
-                // Section header line is handled by the border box top border now
-            } else if p.2.starts_with("float3") {
-                if let Some(f) = &self.float3s[i] {
-                    quads.extend(f.extra_quads());
-                }
-            } else if p.2 == "code" {
-                quads.push((r.0, r.1 + 18.0, r.2, r.3 - 18.0, [0.08, 0.08, 0.10, 1.0]));
-                let border_color = if self.focused_param == Some(i) {
-                    [0.25, 0.45, 0.85, 1.0]
-                } else {
-                    [0.20, 0.20, 0.25, 1.0]
-                };
-                let (bx, by, bw, bh) = (r.0, r.1 + 18.0, r.2, r.3 - 18.0);
-                quads.push((bx, by, bw, 1.0, border_color));
-                quads.push((bx, by + bh - 1.0, bw, 1.0, border_color));
-                quads.push((bx, by, 1.0, bh, border_color));
-                quads.push((bx + bw - 1.0, by, 1.0, bh, border_color));
-            } else if p.2 == "text" {
-                let box_x = self.x + 100.0;
-                let box_w = (self.w - 100.0 - 16.0).max(10.0);
-                quads.push((box_x, r.1, box_w, r.3, [0.08, 0.08, 0.10, 1.0]));
-                let border_color = if self.focused_param == Some(i) {
-                    [0.25, 0.45, 0.85, 1.0]
-                } else {
-                    [0.20, 0.20, 0.25, 1.0]
-                };
-                let (bx, by, bw, bh) = (box_x, r.1, box_w, r.3);
-                let border_t = 1.0;
-                quads.push((bx, by, bw, border_t, border_color));
-                quads.push((bx, by + bh - border_t, bw, border_t, border_color));
-                quads.push((bx, by, border_t, bh, border_color));
-                quads.push((bx + bw - border_t, by, border_t, bh, border_color));
-            } else if p.2.starts_with("spinbox") {
-                if let Some(sb) = &self.spinboxes[i] {
-                    quads.push((sb.base.x, sb.base.y, sb.base.w, sb.base.h, sb.color()));
-                    quads.extend(sb.extra_quads());
-                }
-            }
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let rects = self.get_param_rects();
-        let mut labels = Vec::new();
-        for (i, (name, value, ptype)) in self.display_params.iter().enumerate() {
-            let r = rects[i];
-            if ptype.starts_with("slider") {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.x + 8.0,
-                    y: r.1,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-                if let Some(s) = &self.sliders[i] {
-                    labels.extend(s.text_labels());
-                }
-            } else if ptype.starts_with("float3") {
-                if let Some(f) = &self.float3s[i] {
-                    labels.extend(f.text_labels());
-                }
-            } else if ptype == "section" {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.x + 12.0,
-                    y: r.1 + 2.0,
-                    font_size: 13.0,
-                    color: [0xee, 0xee, 0xf0],
-                });
-            } else if ptype == "code" {
-                labels.push(TextLabel {
-                    text: format!("{}:\n{}", name, value),
-                    x: self.x + 12.0,
-                    y: r.1,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-            } else if ptype.starts_with("spinbox") {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-                if let Some(sb) = &self.spinboxes[i] {
-                    labels.extend(sb.text_labels());
-                }
-            } else if ptype == "text" {
-                labels.push(TextLabel {
-                    text: name.clone(),
-                    x: self.x + 8.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-                let val_text = if self.focused_param == Some(i) {
-                    format!("{}|", value)
-                } else {
-                    value.clone()
-                };
-                labels.push(TextLabel {
-                    text: val_text,
-                    x: self.x + 106.0,
-                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
-                    font_size: 12.0,
-                    color: [0xee, 0xee, 0xf0],
-                });
-            } else {
-                labels.push(TextLabel {
-                    text: format!("{}: {}", name, value),
-                    x: self.x + 8.0,
-                    y: r.1,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-            }
-        }
-        labels
-    }
-}
-
-pub struct Canvas {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
-
-impl Canvas {
-    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
-}
-
-impl Widget for Canvas {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, _px: f32, _py: f32) -> bool { false }
-}
-
-const DROPDOWN_ITEM_H: f32 = 22.0;
-const CHECKBOX_WIDTH: f32 = 16.0;
-pub struct MenuBar {
-    x: f32, y: f32, w: f32, h: f32,
-    hovering: bool,
-    pub title: String,
-    pub menus: Vec<Box<Menu>>,
-    pub menu_items: Vec<String>,
-    pub vertical_items: Vec<String>,
-    pub menu_dropdowns: Vec<Vec<String>>,
-    pub menu_dropdown_checked: Vec<Vec<Option<bool>>>,
-    pub hovered_menu: Option<usize>,
-    pub open_menu: Option<usize>,
-    pub hovered_dropdown: Option<usize>,
-    pub clicked_dropdown: Option<(usize, usize)>,
-    pub was_open: Option<usize>,
-    pub vertical: bool,
-    pub visible: bool,
-    pub focused: bool,
-    pub z_level: i32,
-    pub center_items: bool,
-    pub curved_circle: Option<(f32, f32, f32)>,
-    pub title_pos: Option<(f32, f32)>,
-    pub title_buf: Option<glyphon::Buffer>,
-    pub curved_title_char_bufs: Vec<glyphon::Buffer>,
-    pub network_opacity: f32,
-}
-
-impl MenuBar {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            x, y, w, h, hovering: false,
-            title: String::new(),
-            menus: Vec::new(),
-            menu_items: Vec::new(),
-            vertical_items: Vec::new(),
-            menu_dropdowns: Vec::new(),
-            menu_dropdown_checked: Vec::new(),
-            hovered_menu: None,
-            open_menu: None,
-            hovered_dropdown: None,
-            clicked_dropdown: None,
-            was_open: None,
-            vertical: false,
-            visible: true,
-            focused: false,
-            z_level: 100,
-            center_items: false,
-            curved_circle: None,
-            title_pos: None,
-            title_buf: None,
-            curved_title_char_bufs: Vec::new(),
-            network_opacity: 1.0,
-        }
-    }
-
-    pub fn with_center_items(mut self, center: bool) -> Self {
-        self.center_items = center;
-        self
-    }
-
-    pub fn with_title(mut self, title: &str) -> Self {
-        self.title = title.to_string();
-        self
-    }
-
-    pub fn with_item(mut self, label: &str, items: &[&str]) -> Self {
-        self.menu_items.push(label.to_string());
-        self.vertical_items.push(label.to_string());
-        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
-        self.menu_dropdown_checked.push(vec![None; items.len()]);
-
-        let item_strs: Vec<String> = items.iter().map(|s| s.to_string()).collect();
-        let mut menu = Menu::new(label, label, &item_strs);
-        menu.vertical = self.vertical;
-        self.menus.push(Box::new(menu));
-        self
-    }
-
-    pub fn with_item_vh(mut self, horizontal_label: &str, vertical_label: &str, items: &[&str]) -> Self {
-        self.menu_items.push(horizontal_label.to_string());
-        self.vertical_items.push(vertical_label.to_string());
-        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
-        self.menu_dropdown_checked.push(vec![None; items.len()]);
-
-        let item_strs: Vec<String> = items.iter().map(|s| s.to_string()).collect();
-        let mut menu = Menu::new(horizontal_label, vertical_label, &item_strs);
-        menu.vertical = self.vertical;
-        self.menus.push(Box::new(menu));
-        self
-    }
-
-    pub fn with_vertical(mut self, vertical: bool) -> Self {
-        self.vertical = vertical;
-        for menu in &mut self.menus {
-            menu.vertical = vertical;
-        }
-        self
-    }
-
-    pub fn with_z_index(mut self, z: i32) -> Self {
-        self.z_level = z;
-        self
-    }
-
-    fn item_y_vertical(&self, idx: usize) -> f32 {
-        let mut y = 8.0;
-        if !self.title.is_empty() {
-            y += 24.0;
-        }
-        y + idx as f32 * 24.0
-    }
-
-    fn item_h_vertical(&self) -> f32 {
-        24.0
-    }
-}
-
-impl Widget for MenuBar {
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        if !self.visible {
-            return (0.0, 0.0, 0.0, 0.0);
-        }
-        if self.vertical {
-            let total_h = if self.menus.is_empty() {
-                self.h
-            } else {
-                let last_idx = self.menus.len() - 1;
-                self.item_y_vertical(last_idx) + self.item_h_vertical()
-            };
-            (self.x, self.y, self.w, total_h)
-        } else {
-            (self.x, self.y, self.w, self.h)
-        }
-    }
-
-    fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
-        self.curved_circle = circle;
-        if circle.is_none() {
-            for menu in &mut self.menus {
-                menu.curved_arc = None;
-            }
-        }
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.x = x;
-        self.y = y;
-        self.w = w;
-        self.h = h;
-
-        let parent_ptr = self as *mut MenuBar as *mut (dyn Widget + 'static);
-
-        if self.vertical {
-            let mut cy = 8.0;
-            if !self.title.is_empty() {
-                cy += 24.0;
-            }
-            for menu in &mut self.menus {
-                let ih = 24.0;
-                menu.set_rect(x, y + cy, w, ih);
-                menu.set_parent(Some(parent_ptr));
-                cy += ih;
-            }
-        } else {
-            if let Some((ccx, ccy, ccr)) = self.curved_circle {
-                let r_mid = ccr - h / 2.0;
-                let mut total_width = 8.0;
-                if !self.title.is_empty() {
-                    total_width += self.title.len() as f32 * 7.5 + 24.0;
-                }
-                for menu in &self.menus {
-                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-                }
-                
-                let total_angular_width = total_width / r_mid;
-                let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
-                let mut current_angle = start_angle;
-                
-                if !self.title.is_empty() {
-                    let title_w = self.title.len() as f32 * 7.5 + 24.0;
-                    let dtheta_title = title_w / r_mid;
-                    let theta_title = current_angle + dtheta_title / 2.0;
-                    
-                    let tx = ccx + r_mid * theta_title.cos() - title_w / 2.0 + 8.0;
-                    let ty = ccy + r_mid * theta_title.sin() - h / 2.0;
-                    self.title_pos = Some((tx, ty));
-                    current_angle += dtheta_title;
-                } else {
-                    self.title_pos = None;
-                }
-                
-                for menu in &mut self.menus {
-                    let iw = menu.active_title().len() as f32 * 7.5 + 16.0;
-                    let dtheta_menu = iw / r_mid;
-                    let theta_menu = current_angle + dtheta_menu / 2.0;
-                    
-                    let mx = ccx + r_mid * theta_menu.cos() - iw / 2.0;
-                    let my = ccy + r_mid * theta_menu.sin() - h / 2.0;
-                    
-                    menu.set_rect(mx, my, iw, h);
-                    menu.set_parent(Some(parent_ptr));
-                    menu.curved_arc = Some((ccx, ccy, ccr, h, current_angle, current_angle + dtheta_menu));
-                    current_angle += dtheta_menu;
-                }
-            } else {
-                self.title_pos = None;
-                let mut cx = 8.0;
-                if self.center_items {
-                    let mut total_width = 8.0;
-                    if !self.title.is_empty() {
-                        total_width += self.title.len() as f32 * 7.5 + 24.0;
-                    }
-                    for menu in &self.menus {
-                        total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-                    }
-                    if self.w > total_width {
-                        cx = (self.w - total_width) / 2.0;
-                    }
-                }
-                if !self.title.is_empty() {
-                    cx += self.title.len() as f32 * 7.5 + 24.0;
-                }
-                for menu in &mut self.menus {
-                    let iw = menu.active_title().len() as f32 * 7.5 + 16.0;
-                    menu.set_rect(x + cx, y, iw, h);
-                    menu.set_parent(Some(parent_ptr));
-                    cx += iw;
-                }
-            }
-        }
-    }
-
-    fn set_network_opacity(&mut self, opacity: f32) {
-        self.network_opacity = opacity;
-    }
-
-    fn color(&self) -> [f32; 4] {
-        if !self.visible {
-            [0.0, 0.0, 0.0, 0.0]
-        } else if self.focused {
-            let mut c = colors::PANEL_MENU_FOCUSED;
-            c[3] *= self.network_opacity;
-            c
-        } else {
-            let mut c = colors::PANEL_MENU_BG;
-            c[3] *= self.network_opacity;
-            c
-        }
-    }
-
-    fn set_hovered(&mut self, v: bool) {
-        self.hovering = v;
-    }
-
-    fn hovered(&self) -> bool {
-        self.hovering
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some((ccx, ccy, ccr)) = self.curved_circle {
-            let dx = px - ccx;
-            let dy = py - ccy;
-            let dist = (dx * dx + dy * dy).sqrt();
-            if dist >= ccr - self.h && dist <= ccr {
-                let angle = dy.atan2(dx);
-                let mut norm_angle = angle;
-                if norm_angle < 0.0 {
-                    norm_angle += 2.0 * std::f32::consts::PI;
-                }
-                
-                let r_mid = ccr - self.h / 2.0;
-                let mut total_width = 8.0;
-                if !self.title.is_empty() {
-                    total_width += self.title.len() as f32 * 7.5 + 24.0;
-                }
-                for menu in &self.menus {
-                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-                }
-                let total_angular_width = total_width / r_mid;
-                let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
-                let end_angle = 1.5 * std::f32::consts::PI + total_angular_width / 2.0;
-                
-                if norm_angle >= start_angle && norm_angle <= end_angle {
-                    return true;
-                }
-            }
-            for menu in &self.menus {
-                if menu.hit_test(px, py) {
-                    return true;
-                }
-            }
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
-            return true;
-        }
-        for menu in &self.menus {
-            if menu.hit_test(px, py) {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        self.set_rect(rx, ry, rw, rh);
-
-        if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/home/lsgalante/Dropbox/Clear/debug.txt") {
-            use std::io::Write;
-            let _ = writeln!(f, "MenuBar::on_cursor_moved px={}, py={} curved={:?} rect={:?}", px, py, self.curved_circle, (rx, ry, rw, rh));
-        }
-
-        let mut changed = false;
-        self.hovered_menu = None;
-        for (idx, menu) in self.menus.iter_mut().enumerate() {
-            if menu.cursor_moved(px, py) {
-                changed = true;
-            }
-            if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/home/lsgalante/Dropbox/Clear/debug.txt") {
-                use std::io::Write;
-                let _ = writeln!(f, "  Menu[{}] active_title={} curved={:?} hovered={} hit={}", idx, menu.active_title(), menu.curved_arc, menu.hovered(), menu.hit_test(px, py));
-            }
-            if menu.hovered() {
-                self.hovered_menu = Some(idx);
-            }
-        }
-        changed
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        self.set_rect(rx, ry, rw, rh);
-
-        let mut changed = false;
-        for menu in &mut self.menus {
-            let res = menu.mouse_input(button, state, px, py);
-            if res {
-                changed = true;
-            }
-        }
-        if !self.is_menu_open() {
-            self.unfocus();
-        }
-        changed
-    }
-
-    fn focus(&mut self) {
-        if self.is_menu_open() {
-            self.focused = true;
-            for menu in &mut self.menus {
-                if menu.is_menu_open() {
-                    menu.focus();
-                    return;
-                }
-            }
-        } else {
-            self.focused = false;
-            focus::clear_if_matches(self);
-            return;
-        }
-        self.focused = true;
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        self.focused = false;
-        focus::clear_if_matches(self);
-        for menu in &mut self.menus {
-            menu.unfocus();
-        }
-    }
-
-    fn focused(&self) -> bool {
-        self.focused || self.is_menu_open()
-    }
-
-    fn set_selected(&mut self, selected: bool) {
-        self.focused = selected;
-        if !selected {
-            for menu in &mut self.menus {
-                menu.set_selected(false);
-            }
-        }
-    }
-
-    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
-        for menu in &mut self.menus {
-            menu.set_modifiers(ctrl, shift, alt);
-        }
-    }
-
-    fn menu_names(&self) -> Vec<String> {
-        self.menu_items.clone()
-    }
-
-    fn menu_click(&mut self) -> Option<(usize, usize)> {
-        for (idx, menu) in self.menus.iter_mut().enumerate() {
-            if let Some((_, item_idx)) = menu.menu_click() {
-                return Some((idx, item_idx));
-            }
-        }
-        None
-    }
-
-    fn set_item_checked(&mut self, menu_idx: usize, item_idx: usize, checked: bool) {
-        if let Some(menu) = self.menu_dropdown_checked.get_mut(menu_idx) {
-            if item_idx < menu.len() {
-                menu[item_idx] = Some(checked);
-            }
-        }
-        if let Some(menu) = self.menus.get_mut(menu_idx) {
-            menu.set_item_checked(0, item_idx, checked);
-        }
-    }
-
-    fn set_menu_items(&mut self, menu_idx: usize, items: &[String]) {
-        if menu_idx < self.menu_dropdowns.len() {
-            self.menu_dropdowns[menu_idx] = items.to_vec();
-            self.menu_dropdown_checked[menu_idx] = vec![Some(false); items.len()];
-        }
-        if let Some(menu) = self.menus.get_mut(menu_idx) {
-            menu.items = items.to_vec();
-            menu.item_checked = vec![Some(false); items.len()];
-            menu.item_bufs.clear();
-        }
-    }
-
-    fn is_menu_bar(&self) -> bool {
-        self.visible
-    }
-
-    fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> {
-        if !self.visible {
-            return None;
-        }
-        for (idx, menu) in self.menus.iter().enumerate() {
-            if menu.hit_test(px, py) {
-                let mut formatted_items = Vec::new();
-                for (i, item) in menu.items.iter().enumerate() {
-                    let checked = menu.item_checked.get(i).and_then(|&v| v);
-                    let prefix = match checked {
-                        Some(true) => "✓ ",
-                        Some(false) => "  ",
-                        None => "",
-                    };
-                    formatted_items.push(format!("{}{}", prefix, item));
-                }
-                return Some((idx, menu.active_title().to_string(), formatted_items, menu.base.x, menu.base.y, menu.base.w, menu.base.h));
-            }
-        }
-        None
-    }
-
-    fn trigger_menu_click(&mut self, menu_idx: usize, item_idx: usize) {
-        if let Some(menu) = self.menus.get_mut(menu_idx) {
-            menu.clicked_item = Some(item_idx);
-        }
-    }
-
-    fn is_menu_open(&self) -> bool {
-        self.visible && self.menus.iter().any(|m| m.is_menu_open())
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        for menu in &self.menus {
-            quads.extend(menu.extra_quads());
-        }
-        quads
-    }
-
-    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut arcs = Vec::new();
-        for menu in &self.menus {
-            arcs.extend(menu.extra_arcs());
-        }
-        arcs
-    }
-
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        if !self.visible {
-            return;
-        }
-        if !self.title.is_empty() {
-            if let Some((_ccx, _ccy, _ccr)) = self.curved_circle {
-                if self.curved_title_char_bufs.len() != self.title.chars().count() {
-                    self.curved_title_char_bufs = self.title.chars()
-                        .map(|c| make_widget_text_buffer(fs, &c.to_string(), 12.0, "Outfit"))
-                        .collect();
-                }
-                self.title_buf = None;
-            } else {
-                if self.title_buf.is_none() {
-                    self.title_buf = Some(make_widget_text_buffer(fs, &self.title, 12.0, "Outfit"));
-                }
-                self.curved_title_char_bufs.clear();
-            }
-        } else {
-            self.title_buf = None;
-            self.curved_title_char_bufs.clear();
-        }
-        for menu in &mut self.menus {
-            menu.prepare_text(fs);
-        }
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut items = Vec::new();
-        let color = glyphon::Color::rgb(0xaa, 0xaa, 0xbb);
-
-        if let Some((ccx, ccy, ccr)) = self.curved_circle {
-            let r_mid = ccr - self.h / 2.0;
-            let mut total_width = 8.0;
-            if !self.title.is_empty() {
-                total_width += self.title.len() as f32 * 7.5 + 24.0;
-            }
-            for menu in &self.menus {
-                total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-            }
-            let total_angular_width = total_width / r_mid;
-            let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
-            let mut current_angle = start_angle;
-
-            if !self.title.is_empty() {
-                let title_w = self.title.len() as f32 * 7.5 + 24.0;
-                let dtheta_title = title_w / r_mid;
-
-                let char_widths: Vec<f32> = self.title.chars().map(|c| {
-                    TextLabel::estimate_width(&c.to_string(), 12.0)
-                }).collect();
-                let total_chars_width: f32 = char_widths.iter().sum();
-                let mid_angle = (current_angle + current_angle + dtheta_title) / 2.0;
-                let angular_width = total_chars_width / r_mid;
-                let text_start_angle = mid_angle - angular_width / 2.0;
-                let mut cur_char_angle = text_start_angle;
-
-                for (char_idx, c_buf) in self.curved_title_char_bufs.iter().enumerate() {
-                    let cw = char_widths[char_idx];
-                    let dtheta = cw / r_mid;
-                    let char_center_angle = cur_char_angle + dtheta / 2.0;
-
-                    let tx = ccx + r_mid * char_center_angle.cos() - cw / 2.0;
-                    let ty = ccy + r_mid * char_center_angle.sin() - 12.0 / 2.0;
-
-                    items.push((c_buf, tx, ty, color));
-                    cur_char_angle += dtheta;
-                }
-                current_angle += dtheta_title;
-            }
-        } else {
-            let mut start_x = 8.0;
-            if self.center_items {
-                let mut total_width = 8.0;
-                if !self.title.is_empty() {
-                    total_width += self.title.len() as f32 * 7.5 + 24.0;
-                }
-                for menu in &self.menus {
-                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-                }
-                if self.w > total_width {
-                    start_x = (self.w - total_width) / 2.0;
-                }
-            }
-            if let Some(ref title_buf) = self.title_buf {
-                items.push((title_buf, self.x + start_x, self.y + 7.0, color));
-            }
-        }
-
-        for menu in &self.menus {
-            items.extend(menu.get_text_items());
-        }
-        items
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        if let Some((ccx, ccy, ccr)) = self.curved_circle {
-            let r_mid = ccr - self.h / 2.0;
-            let mut total_width = 8.0;
-            if !self.title.is_empty() {
-                total_width += self.title.len() as f32 * 7.5 + 24.0;
-            }
-            for menu in &self.menus {
-                total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-            }
-            let total_angular_width = total_width / r_mid;
-            let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
-            let mut current_angle = start_angle;
-
-            if !self.title.is_empty() {
-                let title_w = self.title.len() as f32 * 7.5 + 24.0;
-                let dtheta_title = title_w / r_mid;
-                labels.extend(TextLabel::curved_layout(
-                    &self.title,
-                    ccx, ccy, r_mid,
-                    current_angle, current_angle + dtheta_title,
-                    12.0,
-                    [0xaa, 0xaa, 0xbb],
-                ));
-                current_angle += dtheta_title;
-            }
-        } else {
-            let mut start_x = 8.0;
-            if self.center_items {
-                let mut total_width = 8.0;
-                if !self.title.is_empty() {
-                    total_width += self.title.len() as f32 * 7.5 + 24.0;
-                }
-                for menu in &self.menus {
-                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
-                }
-                if self.w > total_width {
-                    start_x = (self.w - total_width) / 2.0;
-                }
-            }
-            if !self.title.is_empty() {
-                labels.push(TextLabel {
-                    text: self.title.clone(),
-                    x: self.x + start_x,
-                    y: self.y + 7.0,
-                    font_size: 12.0,
-                    color: [0xaa, 0xaa, 0xbb],
-                });
-            }
-        }
-        for menu in &self.menus {
-            labels.extend(menu.text_labels());
-        }
-        labels
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-        for menu in &mut self.menus {
-            menu.set_visible(visible);
-        }
-    }
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> {
-        self.menus.iter().map(|m| {
-            let ptr: *const dyn Widget = &**m as &dyn Widget;
-            ptr as *mut (dyn Widget + 'static)
-        }).collect()
-    }
-
-    fn z_index(&self) -> i32 {
-        self.z_level
-    }
-
-    fn set_center_items(&mut self, center: bool) {
-        self.center_items = center;
-    }
-
-    fn menu_items_list(&self) -> Vec<Vec<String>> {
-        self.menu_dropdowns.clone()
-    }
-
-    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
-        self.menu_dropdown_checked.clone()
-    }
-}
-
-impl Drop for MenuBar {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Menu {
-    pub base: WidgetBase,
-    pub title: String,
-    pub vertical_title: String,
-    pub items: Vec<String>,
-    pub item_checked: Vec<Option<bool>>,
-    pub open: bool,
-    pub vertical: bool,
-    hovered_item: Option<usize>,
-    clicked_item: Option<usize>,
-    was_open: Option<usize>,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-    pub curved_arc: Option<(f32, f32, f32, f32, f32, f32)>,
-    pub title_buf: Option<glyphon::Buffer>,
-    pub item_bufs: Vec<glyphon::Buffer>,
-    pub check_buf: Option<glyphon::Buffer>,
-    pub curved_char_bufs: Vec<glyphon::Buffer>,
-}
-
-impl Menu {
-    pub fn new(title: &str, vertical_title: &str, items: &[String]) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            title: title.to_string(),
-            vertical_title: vertical_title.to_string(),
-            items: items.to_vec(),
-            item_checked: vec![None; items.len()],
-            open: false,
-            vertical: false,
-            hovered_item: None,
-            clicked_item: None,
-            was_open: None,
-            parent: None,
-            children: Vec::new(),
-            curved_arc: None,
-            title_buf: None,
-            item_bufs: Vec::new(),
-            check_buf: None,
-            curved_char_bufs: Vec::new(),
-        }
-    }
-
-    pub fn active_title(&self) -> &str {
-        if self.vertical {
-            &self.vertical_title
-        } else {
-            &self.title
-        }
-    }
-
-    fn dropdown_rect(&self) -> (f32, f32, f32, f32) {
-        let dh = self.items.len() as f32 * DROPDOWN_ITEM_H;
-        let mut max_len = 0;
-        for item in &self.items {
-            max_len = max_len.max(item.len());
-        }
-        let dw = (max_len as f32 * 7.5 + 40.0).max(120.0);
-        let dx = if self.vertical {
-            self.base.x + self.base.w
-        } else {
-            self.base.x
-        };
-        let dy = if self.vertical {
-            self.base.y
-        } else {
-            self.base.y + self.base.h
-        };
-        (dx, dy, dw, dh)
-    }
-}
-impl Widget for Menu {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn label(&self) -> Option<String> {
-        Some(self.active_title().to_string())
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            let dx = px - cx;
-            let dy = py - cy;
-            let dist = (dx * dx + dy * dy).sqrt();
-            if dist >= r - thickness && dist <= r {
-                let angle = dy.atan2(dx);
-                let mut norm_angle = angle;
-                if norm_angle < 0.0 {
-                    norm_angle += 2.0 * std::f32::consts::PI;
-                }
-                if norm_angle >= start_angle && norm_angle <= end_angle {
-                    return true;
-                }
-            }
-            if self.open {
-                let (dx, dy, dw, dh) = self.dropdown_rect();
-                if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                    return true;
-                }
-            }
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
-            return true;
-        }
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        self.was_open = None;
-        let was_hovering = self.base.hovered;
-        self.base.hovered = self.hit_test(px, py);
-        let old_item = self.hovered_item;
-        self.hovered_item = None;
-
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if di < self.items.len() {
-                    self.hovered_item = Some(di);
-                }
-            }
-        }
-
-        was_hovering != self.base.hovered || old_item != self.hovered_item
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left || state != ElementState::Pressed {
-            return false;
-        }
-        if !self.hit_test(px, py) {
-            return false;
-        }
-
-        // Check dropdown click if open
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
-                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
-                if di < self.items.len() {
-                    self.clicked_item = Some(di);
-                    self.open = false;
-                    return true;
-                }
-            }
-        }
-
-        // Since we passed hit_test and didn't click dropdown, it's a click on the header title
-        if self.was_open == Some(0) || self.open {
-            self.open = false;
-            self.was_open = None;
-        } else {
-            self.open = true;
-            self.was_open = None;
-            focus::set_focused(self);
-        }
-        true
-    }
-
-    fn focus(&mut self) {
-        self.open = true;
-        self.base.focused = true;
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        if self.open {
-            self.was_open = Some(0);
-        }
-        self.open = false;
-        self.base.focused = false;
-        focus::clear_if_matches(self);
-        self.hovered_item = None;
-    }
-
-    fn set_selected(&mut self, selected: bool) {
-        self.base.focused = selected;
-        if !selected {
-            self.open = false;
-            self.was_open = None;
-            self.hovered_item = None;
-            focus::clear_if_matches(self);
-        }
-    }
-
-    fn menu_click(&mut self) -> Option<(usize, usize)> {
-        self.clicked_item.take().map(|i| (0, i))
-    }
-
-    fn set_item_checked(&mut self, _menu_idx: usize, item_idx: usize, checked: bool) {
-        if item_idx < self.item_checked.len() {
-            self.item_checked[item_idx] = Some(checked);
-            self.item_bufs.clear();
-        }
-    }
-
-    fn is_menu_open(&self) -> bool {
-        self.open
-    }
-
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        if self.curved_arc.is_some() {
-            None
-        } else {
-            let hc = self.highlight_color()?;
-            Some((self.base.x, self.base.y, self.base.w, self.base.h, hc))
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if self.open {
-            let (dx, dy, dw, dh) = self.dropdown_rect();
-            if dh > 0.0 {
-                quads.push((dx, dy, dw, dh, colors::PANEL_MENU_BG));
-                if let Some(di) = self.hovered_item {
-                    quads.push((dx, dy + di as f32 * DROPDOWN_ITEM_H, dw, DROPDOWN_ITEM_H, colors::PANEL_MENU_HOVER));
-                }
-            }
-        }
-        quads
-    }
-
-    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
-        let mut arcs = Vec::new();
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            if self.base.hovered && !self.open {
-                arcs.push((cx, cy, r, thickness, start_angle, end_angle, colors::PANEL_MENU_HOVER));
-            }
-        }
-        arcs
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            let r_mid = r - thickness / 2.0;
-            labels.extend(TextLabel::curved_layout(
-                &self.active_title(),
-                cx, cy, r_mid,
-                start_angle, end_angle,
-                12.0,
-                [0xcc, 0xcc, 0xd4],
-            ));
-        } else {
-            labels.push(TextLabel {
-                text: self.active_title().to_string(),
-                x: self.base.x + 8.0,
-                y: self.base.y + 7.0,
-                font_size: 12.0,
-                color: [0xcc, 0xcc, 0xd4],
-            });
-        }
-        if self.open {
-            let (dx, dy, _, _) = self.dropdown_rect();
-            for (i, item) in self.items.iter().enumerate() {
-                let checked = self.item_checked.get(i).and_then(|&v| v);
-                let prefix = match checked {
-                    Some(true) => "\u{2713} ",
-                    Some(false) => "  ",
-                    None => "",
-                };
-                labels.push(TextLabel {
-                    text: format!("{}{}", prefix, item),
-                    x: dx + 8.0,
-                    y: dy + i as f32 * DROPDOWN_ITEM_H + 5.0,
-                    font_size: 12.0,
-                    color: [0xcc, 0xcc, 0xd4],
-                });
-            }
-        }
-        labels
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.base.hovered = false;
-        if !visible {
-            self.open = false;
-            self.was_open = None;
-            self.hovered_item = None;
-            focus::clear_if_matches(self);
-        }
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> {
-        self.parent
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) {
-        self.parent = parent;
-    }
-
-    fn z_index(&self) -> i32 {
-        100
-    }
-
-    fn menu_items(&self) -> Vec<String> {
-        self.items.clone()
-    }
-
-    fn menu_item_checked(&self) -> Vec<Option<bool>> {
-        self.item_checked.clone()
-    }
-
-    fn is_vertical(&self) -> bool {
-        self.vertical
-    }
-
-    fn focused(&self) -> bool {
-        self.base.focused
-    }
-
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        let title_text = self.active_title();
-        if let Some((_cx, _cy, _r, _thickness, _start_angle, _end_angle)) = self.curved_arc {
-            if self.curved_char_bufs.len() != title_text.chars().count() {
-                self.curved_char_bufs = title_text.chars()
-                    .map(|c| make_widget_text_buffer(fs, &c.to_string(), 12.0, "Outfit"))
-                    .collect();
-            }
-            self.title_buf = None;
-        } else {
-            if self.title_buf.is_none() {
-                self.title_buf = Some(make_widget_text_buffer(fs, title_text, 12.0, "Outfit"));
-            }
-            self.curved_char_bufs.clear();
-        }
-
-        if self.open {
-            if self.item_bufs.len() != self.items.len() {
-                self.item_bufs = self.items.iter().enumerate().map(|(i, item)| {
-                    let checked = self.item_checked.get(i).and_then(|&v| v);
-                    let prefix = match checked {
-                        Some(true) => "\u{2713} ",
-                        Some(false) => "  ",
-                        None => "",
-                    };
-                    let text = format!("{}{}", prefix, item);
-                    make_widget_text_buffer(fs, &text, 12.0, "Outfit")
-                }).collect();
-            }
-        } else {
-            self.item_bufs.clear();
-        }
-    }
-
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        let mut items = Vec::new();
-        let color = glyphon::Color::rgb(0xcc, 0xcc, 0xd4);
-
-        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
-            let r_mid = r - thickness / 2.0;
-            let active_title = self.active_title();
-            
-            let char_widths: Vec<f32> = active_title.chars().map(|c| {
-                TextLabel::estimate_width(&c.to_string(), 12.0)
-            }).collect();
-            let total_chars_width: f32 = char_widths.iter().sum();
-            
-            let angular_width = total_chars_width / r_mid;
-            let text_start_angle = (start_angle + end_angle) / 2.0 - angular_width / 2.0;
-            let mut cur_char_angle = text_start_angle;
-            
-            for (char_idx, c_buf) in self.curved_char_bufs.iter().enumerate() {
-                if char_idx < char_widths.len() {
-                    let cw = char_widths[char_idx];
-                    let dtheta = cw / r_mid;
-                    let char_center_angle = cur_char_angle + dtheta / 2.0;
-                    
-                    let tx = cx + r_mid * char_center_angle.cos() - cw / 2.0;
-                    let ty = cy + r_mid * char_center_angle.sin() - 12.0 / 2.0;
-                    
-                    items.push((c_buf, tx, ty, color));
-                    cur_char_angle += dtheta;
-                }
-            }
-        } else {
-            if let Some(ref title_buf) = self.title_buf {
-                items.push((title_buf, self.base.x + 8.0, self.base.y + 7.0, color));
-            }
-        }
-
-        if self.open {
-            let (dx, dy, _, _) = self.dropdown_rect();
-            for (i, item_buf) in self.item_bufs.iter().enumerate() {
-                items.push((
-                    item_buf,
-                    dx + 8.0,
-                    dy + i as f32 * DROPDOWN_ITEM_H + 5.0,
-                    color,
-                ));
-            }
-        }
-
-        items
-    }
-}
-
-unsafe impl Send for Menu {}
-unsafe impl Sync for Menu {}
-
-impl Drop for Menu {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ButtonKind {
-    Primary,
-    Reset,
-    ListRow,
-    CopyIcon,
-}
-
-#[derive(Debug, Clone)]
-pub struct Button {
-    base: WidgetBase,
-    pressed: bool,
-    just_clicked: bool,
-    kind: ButtonKind,
-    pub selected: bool,
-}
-
-impl Button {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            pressed: false,
-            just_clicked: false,
-            kind: ButtonKind::Primary,
-            selected: false,
-        }
-    }
-
-    pub fn new_reset(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            pressed: false,
-            just_clicked: false,
-            kind: ButtonKind::Reset,
-            selected: false,
-        }
-    }
-
-    pub fn new_list_row(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            pressed: false,
-            just_clicked: false,
-            kind: ButtonKind::ListRow,
-            selected: false,
-        }
-    }
-
-    pub fn new_copy_icon(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            pressed: false,
-            just_clicked: false,
-            kind: ButtonKind::CopyIcon,
-            selected: false,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn with_selected(mut self, selected: bool) -> Self {
-        self.selected = selected;
-        self
-    }
-}
-
-impl Widget for Button {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn top_room(&self) -> f32 { 0.0 }
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> { None }
-
-    fn color(&self) -> [f32; 4] {
-        match self.kind {
-            ButtonKind::Primary => {
-                if self.pressed { colors::BUTTON_PRESS }
-                else if self.base.hovered { colors::BUTTON_HOVER }
-                else { colors::BUTTON_IDLE }
-            }
-            ButtonKind::Reset => {
-                if self.pressed { colors::RESET_BTN_PRESS }
-                else if self.base.hovered { colors::RESET_BTN_HOVER }
-                else { colors::RESET_BTN_IDLE }
-            }
-            ButtonKind::ListRow => {
-                if self.selected {
-                    if self.pressed { [0.30, 0.52, 0.78, 0.6] }
-                    else if self.base.hovered { [0.30, 0.52, 0.78, 0.5] }
-                    else { [0.20, 0.40, 0.65, 0.4] }
-                } else {
-                    if self.pressed { [0.20, 0.20, 0.25, 0.25] }
-                    else if self.base.hovered { [0.20, 0.20, 0.25, 0.15] }
-                    else { [0.0, 0.0, 0.0, 0.0] }
-                }
-            }
-            ButtonKind::CopyIcon => {
-                if self.selected {
-                    if self.pressed { [0.30, 0.52, 0.78, 0.5] }
-                    else if self.base.hovered { [0.30, 0.52, 0.78, 0.5] }
-                    else { [0.20, 0.40, 0.65, 0.2] }
-                } else {
-                    if self.pressed { [0.20, 0.20, 0.25, 0.25] }
-                    else if self.base.hovered { [0.20, 0.20, 0.25, 0.25] }
-                    else { [0.0, 0.0, 0.0, 0.0] }
-                }
-            }
-        }
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                if self.hit_test(px, py) {
-                    self.pressed = true;
-                    return true;
-                }
-            }
-            ElementState::Released => {
-                if self.pressed && self.hit_test(px, py) {
-                    self.just_clicked = true;
-                }
-                let was = self.pressed;
-                self.pressed = false;
-                return was;
-            }
-        }
-        false
-    }
-
-    fn take_click(&mut self) -> bool {
-        if self.just_clicked { self.just_clicked = false; true } else { false }
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.label {
-            let font_size = 12.0;
-            let est_w = if label == "📋" {
-                12.0
-            } else {
-                TextLabel::estimate_width(label, font_size)
-            };
-            let color = match self.kind {
-                ButtonKind::ListRow | ButtonKind::CopyIcon => {
-                    if self.selected { [230, 230, 242] }
-                    else { [178, 178, 191] }
-                }
-                _ => [0xcc, 0xcc, 0xd4]
-            };
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x + (self.base.w - est_w) / 2.0,
-                y: self.base.y + (self.base.h - font_size) / 2.0 - 1.0,
-                font_size,
-                color,
-            });
-        }
-        labels
-    }
-    fn set_selected(&mut self, selected: bool) {
-        self.selected = selected;
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        vec![(self.base.x, self.base.y, self.base.w, self.base.h, self.color())]
-    }
-}
-
-pub enum PageButton {
-    Active,
-    Inactive,
-}
-
-pub struct Sidebar {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-}
-
-impl Sidebar {
-    pub fn new(w: f32) -> Self { Self { x: 0.0, y: 0.0, w, h: 0.0, hovered: false } }
-}
-
-impl Widget for Sidebar {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { colors::sidebar_bg_color() }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-}
-
-pub struct Panel {
-    base: WidgetBase,
-    dragging: bool,
-    drag_ox: f32, drag_oy: f32,
-    drag_start_x: f32, drag_start_y: f32,
-    bounds: Option<(f32, f32, f32, f32)>,
-}
-
-impl Panel {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            dragging: false,
-            drag_ox: 0.0,
-            drag_oy: 0.0,
-            drag_start_x: 0.0,
-            drag_start_y: 0.0,
-            bounds: None,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        self.bounds = Some((bx, by, bw, bh));
-    }
-}
-
-impl Widget for Panel {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn top_room(&self) -> f32 { 0.0 }
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> { None }
-
-    fn color(&self) -> [f32; 4] { if self.dragging { colors::PANEL_DRAG } else { colors::PANEL_IDLE } }
-
-    fn set_drag_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        self.bounds = Some((bx, by, bw, bh));
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                if self.hit_test(px, py) {
-                    self.drag_begin(px, py);
-                    return true;
-                }
-            }
-            ElementState::Released => {
-                if self.dragging { self.drag_end(); return true; }
-            }
-        }
-        false
-    }
-
-    fn is_dragging(&self) -> bool { self.dragging }
-    fn draggable(&self) -> bool { true }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        let nx = px - self.drag_ox;
-        let ny = py - self.drag_oy;
-        let (nx, ny) = if let Some((bx, by, bw, bh)) = self.bounds {
-            (nx.clamp(bx, bx + bw - self.base.w), ny.clamp(by, by + bh - self.base.h))
-        } else {
-            (nx, ny)
-        };
-        if (nx - self.base.x).abs() > 0.01 || (ny - self.base.y).abs() > 0.01 {
-            self.base.x = nx;
-            self.base.y = ny;
-            return true;
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        self.dragging = true;
-        self.drag_ox = px - self.base.x;
-        self.drag_oy = py - self.base.y;
-        self.drag_start_x = self.base.x;
-        self.drag_start_y = self.base.y;
-    }
-
-    fn drag_end(&mut self) { self.dragging = false; }
-}
-
-pub struct Node {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    selected: bool,
-    dragging: bool,
-    drag_ox: f32, drag_oy: f32,
-    bounds: Option<(f32, f32, f32, f32)>,
-    grid_snap_x: f32,
-    grid_snap_y: f32,
-    grid_origin_x: f32,
-    grid_origin_y: f32,
-    name: String,
-    pub parameters: Vec<(String, String, String)>,
-    geom_visible: bool,
-    geom_toggled: bool,
-    toggle_hovered: bool,
-}
-
-impl Node {
-    pub fn new(x: f32, y: f32, w: f32, h: f32, name: &str) -> Self {
-        Self {
-            x, y, w, h,
-            hovered: false, selected: false, dragging: false, drag_ox: 0.0, drag_oy: 0.0,
-            bounds: None, grid_snap_x: 0.0, grid_snap_y: 0.0,
-            grid_origin_x: 0.0, grid_origin_y: 0.0,
-            name: name.to_string(), parameters: Vec::new(),
-            geom_visible: true, geom_toggled: false, toggle_hovered: false,
-        }
-    }
-
-    pub fn with_params(mut self, params: &[(&str, &str)]) -> Self {
-        self.parameters = params.iter().map(|(k, v)| (k.to_string(), v.to_string(), "string".to_string())).collect();
-        self
-    }
-
-    pub fn with_grid_snap(mut self, gx: f32, gy: f32) -> Self {
-        self.grid_snap_x = gx;
-        self.grid_snap_y = gy;
-        self
-    }
-
-    fn toggle_rect(&self) -> (f32, f32, f32, f32) {
-        (self.x + self.w - 30.0, self.y + (self.h - 18.0) / 2.0, 18.0, 18.0)
-    }
-}
-
-impl Widget for Node {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] {
-        if self.dragging { colors::node_drag_color() }
-        else if self.selected { colors::node_selected_color() }
-        else { colors::node_color() }
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-
-    fn focus(&mut self) {
-        self.selected = true;
-        focus::set_focused(self);
-    }
-    fn unfocus(&mut self) { self.selected = false; }
-
-    fn node_params(&self) -> Vec<(String, String, String)> { self.parameters.clone() }
-    fn set_display_params(&mut self, params: &[(String, String, String)]) {
-        self.parameters = params.to_vec();
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        vec![TextLabel {
-            text: self.name.clone(),
-            x: self.x + 8.0,
-            y: self.y + (self.h - 12.0) / 2.0,
-            font_size: 14.0,
-            color: [0xcc, 0xcc, 0xd4],
-        }]
-    }
-
-    fn set_drag_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        self.bounds = Some((bx, by, bw, bh));
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was_hovered = self.hovered;
-        self.hovered = self.hit_test(px, py);
-
-        let was_toggle_hovered = self.toggle_hovered;
-        let (tx, ty, tw, th) = self.toggle_rect();
-        self.toggle_hovered = px >= tx && px < tx + tw && py >= ty && py < ty + th;
-
-        was_hovered != self.hovered || was_toggle_hovered != self.toggle_hovered
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                if self.hit_test(px, py) {
-                    let (tx, ty, tw, th) = self.toggle_rect();
-                    if px >= tx && px < tx + tw && py >= ty && py < ty + th {
-                        self.geom_visible = !self.geom_visible;
-                        self.geom_toggled = true;
-                        return true;
-                    }
-                    self.drag_begin(px, py);
-                    return true;
-                }
-            }
-            ElementState::Released => {
-                if self.dragging { self.drag_end(); return true; }
-            }
-        }
-        false
-    }
-
-    fn is_dragging(&self) -> bool { self.dragging }
-    fn draggable(&self) -> bool { !self.toggle_hovered }
-
-    fn set_grid_snap(&mut self, gx: f32, gy: f32) { self.grid_snap_x = gx; self.grid_snap_y = gy; }
-    fn set_grid_origin(&mut self, ox: f32, oy: f32) { self.grid_origin_x = ox; self.grid_origin_y = oy; }
-    fn set_node_name(&mut self, name: &str) { self.name = name.to_string(); }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        let nx = px - self.drag_ox;
-        let ny = py - self.drag_oy;
-        let (nx, ny) = if let Some((bx, by, bw, bh)) = self.bounds {
-            (nx.clamp(bx, bx + bw - self.w), ny.clamp(by, by + bh - self.h))
-        } else {
-            (nx, ny)
-        };
-        let nx = if self.grid_snap_x > 0.0 {
-            let relative = nx - self.grid_origin_x;
-            let snapped = (relative / self.grid_snap_x).round() * self.grid_snap_x;
-            snapped + self.grid_origin_x
-        } else { nx };
-        let ny = if self.grid_snap_y > 0.0 {
-            let relative = ny - self.grid_origin_y;
-            let snapped = (relative / self.grid_snap_y).round() * self.grid_snap_y;
-            snapped + self.grid_origin_y
-        } else { ny };
-        if (nx - self.x).abs() > 0.01 || (ny - self.y).abs() > 0.01 {
-            self.x = nx;
-            self.y = ny;
-            return true;
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        self.dragging = true;
-        self.drag_ox = px - self.x;
-        self.drag_oy = py - self.y;
-    }
-
-    fn drag_end(&mut self) { self.dragging = false; }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let (tx, ty, tw, th) = self.toggle_rect();
-        let bg_color = if self.toggle_hovered {
-            colors::TOGGLE_HOVER
-        } else {
-            colors::TOGGLE_OFF
-        };
-
-        let mut quads = vec![
-            (tx, ty, tw, th, bg_color)
-        ];
-
-        if self.geom_visible {
-            let inset = 3.0;
-            quads.push((tx + inset, ty + inset, tw - inset * 2.0, th - inset * 2.0, colors::TOGGLE_ON));
-        }
-
-        quads
-    }
-
-    fn set_geom_visible(&mut self, visible: bool) { self.geom_visible = visible; }
-    fn geom_visible(&self) -> bool { self.geom_visible }
-    fn take_geom_toggle(&mut self) -> bool { std::mem::take(&mut self.geom_toggled) }
-}
-
-impl Drop for Node {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-fn menu_item_x(title: &str, menu_items: &[String], idx: usize) -> f32 {
-    let mut x = 8.0;
-    if !title.is_empty() {
-        x += title.len() as f32 * 7.5 + 24.0;
-    }
-    for i in 0..idx {
-        x += menu_items[i].len() as f32 * 7.5 + 16.0;
-    }
-    x
-}
-
-fn menu_item_w(menu_items: &[String], idx: usize) -> f32 {
-    menu_items[idx].len() as f32 * 7.5 + 16.0
-}
-
-pub struct Checkbox {
-    base: WidgetBase,
-    checked: bool,
-    just_clicked: bool,
-}
-
-impl Checkbox {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            checked: false,
-            just_clicked: false,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_checked(&mut self, checked: bool) {
-        self.checked = checked;
-    }
-
-    pub fn checked(&self) -> bool {
-        self.checked
-    }
-}
-
-impl Widget for Checkbox {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] { if self.checked { colors::CHECKBOX_CHECKED } else { colors::CHECKBOX_BG } }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Released => {
-                if self.hit_test(px, py) {
-                    self.checked = !self.checked;
-                    self.just_clicked = true;
-                    return true;
-                }
-            }
-            _ => {}
-        }
-        false
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if self.checked {
-            let (x, y, w, h) = self.rect();
-            let pad_x = w * 0.25;
-            let pad_y = h * 0.25;
-            quads.push((x + pad_x, y + pad_y, w - 2.0 * pad_x, h - 2.0 * pad_y, [1.0, 1.0, 1.0, 0.9]));
-        }
-        quads
-    }
-
-    fn take_click(&mut self) -> bool {
-        if self.just_clicked { self.just_clicked = false; true } else { false }
-    }
-    fn value(&self) -> i32 { if self.checked { 1 } else { 0 } }
-}
-
-#[derive(Debug, Clone)]
-pub struct Toggle {
-    base: WidgetBase,
-    toggled: bool,
-    just_toggled: bool,
-}
-
-impl Toggle {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            toggled: false,
-            just_toggled: false,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.base.label = Some(label.to_string());
-    }
-
-    pub fn set_toggled(&mut self, v: bool) {
-        self.toggled = v;
-    }
-
-    pub fn toggled(&self) -> bool {
-        self.toggled
-    }
-}
-
-impl Widget for Toggle {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] { if self.toggled { colors::TOGGLE_ON } else { colors::TOGGLE_OFF } }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Released => {
-                if self.hit_test(px, py) {
-                    self.toggled = !self.toggled;
-                    self.just_toggled = true;
-                    return true;
-                }
-            }
-            _ => {}
-        }
-        false
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let bg = if self.toggled { colors::TOGGLE_ON } else { colors::TOGGLE_OFF };
-        vec![(self.base.x, self.base.y, self.base.w, self.base.h, bg)]
-    }
-
-    fn take_click(&mut self) -> bool {
-        if self.just_toggled { self.just_toggled = false; true } else { false }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Label {
-    base: WidgetBase,
-    font_size: f32,
-    color: [u8; 3],
-}
-
-impl Label {
-    pub fn new(text: &str) -> Self {
-        let mut base = WidgetBase::new();
-        base.label = Some(text.to_string());
-        Self {
-            base,
-            font_size: 12.0,
-            color: [0x83, 0x83, 0x8a],
-        }
-    }
-
-    pub fn with_font_size(mut self, size: f32) -> Self {
-        self.font_size = size;
-        self
-    }
-
-    pub fn with_color(mut self, color: [u8; 3]) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn set_text(&mut self, text: &str) {
-        self.base.label = Some(text.to_string());
-    }
-
-    pub fn set_color(&mut self, color: [u8; 3]) {
-        self.color = color;
-    }
-}
-
-impl Widget for Label {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn top_room(&self) -> f32 { 0.0 }
-
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        vec![TextLabel {
-            text: self.base.label.clone().unwrap_or_default(),
-            x: self.base.x,
-            y: self.base.y + (self.base.h - self.font_size) / 2.0,
-            font_size: self.font_size,
-            color: self.color,
-        }]
-    }
-}
-
-#[derive(Clone)]
-pub struct SectionHeader {
-    base: WidgetBase,
-}
-
-impl SectionHeader {
-    pub fn new(title: &str) -> Self {
-        let mut base = WidgetBase::new();
-        base.label = Some(title.to_string());
-        Self { base }
-    }
-}
-
-impl Widget for SectionHeader {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn top_room(&self) -> f32 { 10.0 }
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        vec![(self.base.x + 8.0, self.base.y + 22.0, self.base.w - 16.0, 1.0, [0.18, 0.18, 0.27, 1.0])]
-    }
-    fn text_labels(&self) -> Vec<TextLabel> {
-        vec![TextLabel {
-            text: self.base.label.clone().unwrap_or_default(),
-            x: self.base.x + 12.0,
-            y: self.base.y,
-            font_size: 14.0,
-            color: [212, 212, 212],
-        }]
-    }
-}
-
-
-#[derive(Debug, Clone)]
-pub struct Svg {
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-    pub quads: Vec<(f32, f32, f32, f32, [f32; 4])>,
-}
-
-impl Svg {
-    pub fn new(svg_data: &[u8], x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
-        let opt = resvg::usvg::Options::default();
-        let fontdb = resvg::usvg::fontdb::Database::new();
-        let tree = resvg::usvg::Tree::from_data(svg_data, &opt, &fontdb).ok()?;
-        
-        let target_w = w as u32;
-        let target_h = h as u32;
-        if target_w == 0 || target_h == 0 {
-            return None;
-        }
-        let mut pixmap = resvg::tiny_skia::Pixmap::new(target_w, target_h)?;
-        
-        let orig_w = tree.size().width();
-        let orig_h = tree.size().height();
-        let sx = target_w as f32 / orig_w;
-        let sy = target_h as f32 / orig_h;
-        let transform = resvg::tiny_skia::Transform::from_scale(sx, sy);
-        
-        resvg::render(&tree, transform, &mut pixmap.as_mut());
-        
-        let mut quads = Vec::new();
-        let pixels = pixmap.data();
-        for row in 0..target_h {
-            for col in 0..target_w {
-                let idx = ((row * target_w + col) * 4) as usize;
-                if idx + 3 < pixels.len() {
-                    let a = pixels[idx + 3] as f32 / 255.0;
-                    if a > 0.0 {
-                        let r = pixels[idx] as f32 / 255.0;
-                        let g = pixels[idx + 1] as f32 / 255.0;
-                        let b = pixels[idx + 2] as f32 / 255.0;
-                        quads.push((
-                            x + col as f32,
-                            y + row as f32,
-                            1.0,
-                            1.0,
-                            [r, g, b, a],
-                        ));
-                    }
-                }
-            }
-        }
-        
-        Some(Self { x, y, w, h, quads })
-    }
-
-    pub fn from_file<P: AsRef<std::path::Path>>(path: P, x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
-        let data = std::fs::read(path).ok()?;
-        Self::new(&data, x, y, w, h)
-    }
-
-    pub fn from_str(svg_str: &str, x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
-        Self::new(svg_str.as_bytes(), x, y, w, h)
-    }
-}
-
-impl Widget for Svg {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        let dx = x - self.x;
-        let dy = y - self.y;
-        for quad in &mut self.quads {
-            quad.0 += dx;
-            quad.1 += dy;
-        }
-        self.x = x;
-        self.y = y;
-        self.w = w;
-        self.h = h;
-    }
-    
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        self.quads.clone()
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Slider {
-    base: WidgetBase,
-    dragging: bool,
-    value: f32,
-    drag_offset: f32,
-    scroll_enabled: bool,
-    show_readout: bool,
-    editing: bool,
-    edit_buffer: String,
-    min: f32,
-    max: f32,
-}
-
-impl Slider {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            dragging: false,
-            value: 0.5,
-            drag_offset: 0.0,
-            scroll_enabled: false,
-            show_readout: false,
-            editing: false,
-            edit_buffer: String::new(),
-            min: 0.0,
-            max: 1.0,
-        }
-    }
-
-    pub fn with_range(mut self, min: f32, max: f32) -> Self {
-        self.min = min;
-        self.max = max;
-        self
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.base.label = Some(label.to_string());
-    }
-
-    pub fn set_range(&mut self, min: f32, max: f32) {
-        self.min = min;
-        self.max = max;
-    }
-
-    pub fn with_scroll(mut self, enabled: bool) -> Self {
-        self.scroll_enabled = enabled;
-        self
-    }
-
-    pub fn set_scroll(&mut self, enabled: bool) {
-        self.scroll_enabled = enabled;
-    }
-
-    pub fn with_value(mut self, val: f32) -> Self {
-        self.value = val.clamp(0.0, 1.0);
-        self
-    }
-
-    pub fn set_value(&mut self, val: f32) {
-        self.value = val.clamp(0.0, 1.0);
-    }
-
-    pub fn with_readout(mut self, enabled: bool) -> Self {
-        self.show_readout = enabled;
-        self
-    }
-
-    pub fn set_readout(&mut self, enabled: bool) {
-        self.show_readout = enabled;
-    }
-
-    pub fn value(&self) -> f32 {
-        self.value
-    }
-
-    pub fn get_scaled_value(&self) -> f32 {
-        self.min + self.value * (self.max - self.min)
-    }
-}
-
-impl Widget for Slider {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] {
-        if self.show_readout {
-            [0.0, 0.0, 0.0, 0.0]
-        } else {
-            colors::slider_track()
-        }
-    }
-
-    fn draggable(&self) -> bool { true }
-    fn is_dragging(&self) -> bool { self.dragging }
-
-    fn drag_update(&mut self, px: f32, _py: f32) -> bool {
-        let (track_x, track_w) = if self.show_readout {
-            let readout_w = 60.0;
-            let gap = 8.0;
-            let tw = (self.base.w - readout_w - gap).max(10.0);
-            (self.base.x, tw)
-        } else {
-            (self.base.x, self.base.w)
-        };
-        let thumb_size = self.base.h * 0.9;
-        let range = track_w - thumb_size;
-        if range > 0.0 {
-            let raw = (px - self.drag_offset - track_x) / range;
-            let new_val = raw.clamp(0.0, 1.0);
-            if (new_val - self.value).abs() > 0.001 {
-                self.value = new_val;
-                if self.editing {
-                    let scaled_val = self.min + self.value * (self.max - self.min);
-                    self.edit_buffer = format!("{:.2}", scaled_val);
-                }
-                return true;
-            }
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, _py: f32) {
-        self.dragging = true;
-        let (track_x, track_w) = if self.show_readout {
-            let readout_w = 60.0;
-            let gap = 8.0;
-            let tw = (self.base.w - readout_w - gap).max(10.0);
-            (self.base.x, tw)
-        } else {
-            (self.base.x, self.base.w)
-        };
-        let thumb_size = self.base.h * 0.9;
-        let thumb_x = track_x + self.value * (track_w - thumb_size);
-        self.drag_offset = px - thumb_x;
-    }
-
-    fn drag_end(&mut self) { self.dragging = false; }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if !self.scroll_enabled {
-            return false;
-        }
-        let (sx, sy, sw, sh) = self.rect();
-        let (track_x, track_w) = if self.show_readout {
-            let readout_w = 60.0;
-            let gap = 8.0;
-            let tw = (sw - readout_w - gap).max(10.0);
-            (sx, tw)
-        } else {
-            (sx, sw)
-        };
-        if px >= track_x && px <= track_x + track_w && py >= sy && py <= sy + sh {
-            let scroll_amount = match delta {
-                MouseScrollDelta::LineDelta(_x, y) => *y,
-                MouseScrollDelta::PixelDelta(pos) => (pos.y as f32) / 120.0,
-            };
-            let step = 0.02;
-            let new_val = (self.value - scroll_amount * step).clamp(0.0, 1.0);
-            if (new_val - self.value).abs() > 0.0001 {
-                self.value = new_val;
-                if self.editing {
-                    let scaled_val = self.min + self.value * (self.max - self.min);
-                    self.edit_buffer = format!("{:.2}", scaled_val);
-                }
-                return true;
-            }
-        }
-        false
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        
-        if self.show_readout {
-            let readout_w = 60.0;
-            let rx = self.base.x + self.base.w - readout_w;
-            
-            if px >= rx && px <= rx + readout_w && py >= self.base.y && py <= self.base.y + self.base.h {
-                if state == ElementState::Pressed {
-                    if !self.editing {
-                        self.editing = true;
-                        let scaled_val = self.min + self.value * (self.max - self.min);
-                        self.edit_buffer = format!("{:.2}", scaled_val);
-                        focus::set_focused(self);
-                    }
-                }
-                return true;
-            }
-        }
-
-        match state {
-            ElementState::Pressed => {
-                let (track_x, track_w) = if self.show_readout {
-                    let readout_w = 60.0;
-                    let gap = 8.0;
-                    let tw = (self.base.w - readout_w - gap).max(10.0);
-                    (self.base.x, tw)
-                } else {
-                    (self.base.x, self.base.w)
-                };
-                let thumb_size = self.base.h * 0.9;
-                let thumb_x = track_x + self.value * (track_w - thumb_size);
-                
-                if px >= track_x && px <= track_x + track_w && py >= self.base.y && py <= self.base.y + self.base.h {
-                    self.dragging = true;
-                    self.drag_offset = px - thumb_x;
-                    return true;
-                }
-                false
-            }
-            ElementState::Released => {
-                if self.dragging {
-                    self.dragging = false;
-                    return true;
-                }
-                false
-            }
-        }
-    }
-
-    fn unfocus(&mut self) {
-        if self.editing {
-            self.editing = false;
-            if let Ok(new_val) = self.edit_buffer.parse::<f32>() {
-                let range = self.max - self.min;
-                if range != 0.0 {
-                    self.value = ((new_val - self.min) / range).clamp(0.0, 1.0);
-                } else {
-                    self.value = 0.0;
-                }
-            }
-        }
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !self.editing { return false; }
-        if event.state != ElementState::Pressed { return false; }
-        
-        match &event.logical_key {
-            Key::Named(NamedKey::Backspace) => {
-                if !self.edit_buffer.is_empty() {
-                    self.edit_buffer.pop();
-                    return true;
-                }
-            }
-            Key::Named(NamedKey::Enter) => {
-                self.unfocus();
-                return true;
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.editing = false;
-                return true;
-            }
-            Key::Character(s) => {
-                for ch in s.chars() {
-                    if ch.is_ascii_digit() || ch == '.' || (ch == '-' && self.edit_buffer.is_empty()) {
-                        self.edit_buffer.push(ch);
-                    }
-                }
-                return true;
-            }
-            _ => {}
-        }
-        false
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        
-        let (track_x, track_w) = if self.show_readout {
-            let readout_w = 60.0;
-            let gap = 8.0;
-            let tw = (self.base.w - readout_w - gap).max(10.0);
-            
-            quads.push((self.base.x, self.base.y, tw, self.base.h, colors::slider_track()));
-            
-            let rx = self.base.x + self.base.w - readout_w;
-            let bg_color = if self.editing {
-                [0.06, 0.10, 0.18, 1.0]
-            } else {
-                [0.10, 0.10, 0.13, 1.0]
-            };
-            quads.push((rx, self.base.y, readout_w, self.base.h, bg_color));
-            
-            if self.base.focused || self.editing {
-                let border_color = [0.20, 0.50, 0.85, 1.0];
-                let border_t = 1.0;
-                quads.push((rx, self.base.y, readout_w, border_t, border_color));
-                quads.push((rx, self.base.y + self.base.h - border_t, readout_w, border_t, border_color));
-                quads.push((rx, self.base.y, border_t, self.base.h, border_color));
-                quads.push((rx + readout_w - border_t, self.base.y, border_t, self.base.h, border_color));
-            }
-
-            (self.base.x, tw)
-        } else {
-            (self.base.x, self.base.w)
-        };
-
-        let thumb_size = self.base.h * 0.9;
-        let thumb_x = track_x + self.value * (track_w - thumb_size);
-        let thumb_y = self.base.y + (self.base.h - thumb_size) / 2.0;
-        let thumb_color = if self.dragging {
-            colors::SLIDER_THUMB_DRAG
-        } else {
-            colors::SLIDER_THUMB
-        };
-        quads.push((thumb_x, thumb_y, thumb_size, thumb_size, thumb_color));
-        
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x,
-                y: self.base.y - 18.0,
-                font_size: 12.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-        
-        if self.show_readout {
-            let readout_w = 60.0;
-            let rx = self.base.x + self.base.w - readout_w;
-            let ry = self.base.y + (self.base.h - 12.0) / 2.0;
-            
-            let text = if self.editing {
-                self.edit_buffer.clone()
-            } else {
-                let scaled_val = self.min + self.value * (self.max - self.min);
-                format!("{:.2}", scaled_val)
-            };
-            
-            labels.push(TextLabel {
-                text,
-                x: rx + 8.0,
-                y: ry,
-                font_size: 12.0,
-                color: [0xee, 0xee, 0xf0],
-            });
-        }
-        
-        labels
-    }
-
-    fn value(&self) -> i32 { (self.value * 100.0) as i32 }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ActiveThumb {
-    Low,
-    High,
-}
-
-pub struct RangeSlider {
-    base: WidgetBase,
-    value_low: f32,
-    value_high: f32,
-    active_thumb: Option<ActiveThumb>,
-    drag_offset: f32,
-}
-
-impl RangeSlider {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            value_low: 0.2,
-            value_high: 0.8,
-            active_thumb: None,
-            drag_offset: 0.0,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn with_values(mut self, low: f32, high: f32) -> Self {
-        self.value_low = low.clamp(0.0, 1.0);
-        self.value_high = high.clamp(self.value_low, 1.0);
-        self
-    }
-
-    pub fn set_values(&mut self, low: f32, high: f32) {
-        self.value_low = low.clamp(0.0, 1.0);
-        self.value_high = high.clamp(self.value_low, 1.0);
-    }
-
-    pub fn values(&self) -> (f32, f32) {
-        (self.value_low, self.value_high)
-    }
-}
-
-impl Widget for RangeSlider {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] { colors::slider_track() }
-
-    fn draggable(&self) -> bool { true }
-    fn is_dragging(&self) -> bool { self.active_thumb.is_some() }
-
-    fn drag_update(&mut self, px: f32, _py: f32) -> bool {
-        let Some(active) = self.active_thumb else { return false; };
-        let thumb_size = self.base.h * 0.9;
-        let range = self.base.w - thumb_size;
-        if range <= 0.0 { return false; }
-        
-        let new_val = ((px - self.drag_offset - self.base.x) / range).clamp(0.0, 1.0);
-        match active {
-            ActiveThumb::Low => {
-                let constrained = new_val.min(self.value_high);
-                if (constrained - self.value_low).abs() > 0.001 {
-                    self.value_low = constrained;
-                    return true;
-                }
-            }
-            ActiveThumb::High => {
-                let constrained = new_val.max(self.value_low);
-                if (constrained - self.value_high).abs() > 0.001 {
-                    self.value_high = constrained;
-                    return true;
-                }
-            }
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, _py: f32) {
-        let thumb_size = self.base.h * 0.9;
-        let range = self.base.w - thumb_size;
-        let thumb_low_x = self.base.x + self.value_low * range;
-        let thumb_high_x = self.base.x + self.value_high * range;
-        let center_low = thumb_low_x + thumb_size / 2.0;
-        let center_high = thumb_high_x + thumb_size / 2.0;
-
-        let active = if (self.value_low - self.value_high).abs() < 0.001 {
-            if px < center_low {
-                ActiveThumb::Low
-            } else {
-                ActiveThumb::High
-            }
-        } else {
-            let dist_low = (px - center_low).abs();
-            let dist_high = (px - center_high).abs();
-            if dist_low < dist_high {
-                ActiveThumb::Low
-            } else {
-                ActiveThumb::High
-            }
-        };
-
-        self.active_thumb = Some(active);
-        let active_x = match active {
-            ActiveThumb::Low => thumb_low_x,
-            ActiveThumb::High => thumb_high_x,
-        };
-        self.drag_offset = px - active_x;
-    }
-
-    fn drag_end(&mut self) {
-        self.active_thumb = None;
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let thumb_size = self.base.h * 0.9;
-        let range = self.base.w - thumb_size;
-        let thumb_low_x = self.base.x + self.value_low * range;
-        let thumb_high_x = self.base.x + self.value_high * range;
-        
-        let thumb_y = self.base.y + (self.base.h - thumb_size) / 2.0;
-        
-        // Highlighted track segment
-        let highlight_x = thumb_low_x + thumb_size / 2.0;
-        let highlight_w = thumb_high_x - thumb_low_x;
-        let highlight_y = self.base.y + self.base.h * 0.35;
-        let highlight_h = self.base.h * 0.3;
-        
-        let low_color = if self.active_thumb == Some(ActiveThumb::Low) {
-            colors::SLIDER_THUMB_DRAG
-        } else {
-            colors::SLIDER_THUMB
-        };
-
-        let high_color = if self.active_thumb == Some(ActiveThumb::High) {
-            colors::SLIDER_THUMB_DRAG
-        } else {
-            colors::SLIDER_THUMB
-        };
-
-        vec![
-            (highlight_x, highlight_y, highlight_w, highlight_h, colors::PROGRESS_FILL),
-            (thumb_low_x, thumb_y, thumb_size, thumb_size, low_color),
-            (thumb_high_x, thumb_y, thumb_size, thumb_size, high_color),
-        ]
-    }
-
-    fn value(&self) -> i32 {
-        ((self.value_low * 100.0) as i32) | (((self.value_high * 100.0) as i32) << 16)
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Float3 {
-    base: WidgetBase,
-    pub values: [f32; 3],
-    mins: [f32; 3],
-    maxs: [f32; 3],
-    labels: [String; 3],
-    dragging_idx: Option<usize>,
-    drag_offset: f32,
-    pub editing_idx: Option<usize>,
-    pub edit_buffer: String,
-}
-
-impl Float3 {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            values: [0.5, 0.5, 0.5],
-            mins: [0.0, 0.0, 0.0],
-            maxs: [1.0, 1.0, 1.0],
-            labels: ["X".to_string(), "Y".to_string(), "Z".to_string()],
-            dragging_idx: None,
-            drag_offset: 0.0,
-            editing_idx: None,
-            edit_buffer: String::new(),
-        }
-    }
-
-    pub fn with_values(mut self, values: [f32; 3]) -> Self {
-        self.values = values;
-        self
-    }
-
-    pub fn with_range(mut self, min: f32, max: f32) -> Self {
-        self.mins = [min, min, min];
-        self.maxs = [max, max, max];
-        self
-    }
-
-    pub fn set_values(&mut self, values: [f32; 3]) {
-        self.values = values;
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn get_row_rects(&self) -> Vec<(f32, f32, f32, f32)> {
-        let mut rects = Vec::new();
-        let by = self.base.y + 20.0;
-        for i in 0..3 {
-            rects.push((self.base.x + 8.0, by + 6.0 + i as f32 * 26.0, self.base.w - 16.0, 20.0));
-        }
-        rects
-    }
-}
-
-impl Widget for Float3 {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.base.x, self.base.y, self.base.w, self.base.h) }
-    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;
-    }
-
-    fn draggable(&self) -> bool { self.dragging_idx.is_some() }
-    fn is_dragging(&self) -> bool { self.dragging_idx.is_some() }
-    
-    fn drag_begin(&mut self, _px: f32, _py: f32) {}
-    
-    fn drag_update(&mut self, px: f32, _py: f32) -> bool {
-        if let Some(i) = self.dragging_idx {
-            let track_x = self.base.x + 100.0;
-            let track_w = self.base.w - 188.0;
-            let thumb_size = 12.0 * 0.9;
-            let range = track_w - thumb_size;
-            if range > 0.0 {
-                let raw = (px - self.drag_offset - track_x) / range;
-                let new_val = raw.clamp(0.0, 1.0);
-                if (new_val - self.values[i]).abs() > 0.001 {
-                    self.values[i] = new_val;
-                    if self.editing_idx == Some(i) {
-                        let scaled_val = self.mins[i] + self.values[i] * (self.maxs[i] - self.mins[i]);
-                        self.edit_buffer = format!("{:.2}", scaled_val);
-                    }
-                    return true;
-                }
-            }
-        }
-        false
-    }
-    
-    fn drag_end(&mut self) {
-        self.dragging_idx = None;
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        let rects = self.get_row_rects();
-        
-        for i in 0..3 {
-            let r = rects[i];
-            let rx = self.base.x + self.base.w - 68.0;
-            let ry = r.1 + 4.0;
-            let rh = 12.0;
-            let readout_w = 60.0;
-            
-            if px >= rx && px <= rx + readout_w && py >= ry && py <= ry + rh {
-                if state == ElementState::Pressed {
-                    if self.editing_idx != Some(i) {
-                        self.unfocus();
-                        self.editing_idx = Some(i);
-                        let scaled_val = self.mins[i] + self.values[i] * (self.maxs[i] - self.mins[i]);
-                        self.edit_buffer = format!("{:.2}", scaled_val);
-                        focus::set_focused(self);
-                    }
-                }
-                return true;
-            }
-        }
-        
-        if state == ElementState::Pressed {
-            for i in 0..3 {
-                let r = rects[i];
-                let track_x = self.base.x + 100.0;
-                let track_w = self.base.w - 188.0;
-                let track_y = r.1 + 4.0;
-                let track_h = 12.0;
-                let thumb_size = track_h * 0.9;
-                let range = track_w - thumb_size;
-                let thumb_x = track_x + self.values[i] * range;
-                
-                if px >= track_x && px <= track_x + track_w && py >= track_y && py <= track_y + track_h {
-                    self.dragging_idx = Some(i);
-                    self.drag_offset = px - thumb_x;
-                    return true;
-                }
-            }
-        } else if state == ElementState::Released {
-            if self.dragging_idx.is_some() {
-                self.dragging_idx = None;
-                return true;
-            }
-        }
-        false
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        let _idx = match self.editing_idx {
-            Some(i) => i,
-            None => return false,
-        };
-        if event.state != ElementState::Pressed { return false; }
-        
-        match &event.logical_key {
-            Key::Named(NamedKey::Backspace) => {
-                if !self.edit_buffer.is_empty() {
-                    self.edit_buffer.pop();
-                    return true;
-                }
-            }
-            Key::Named(NamedKey::Enter) => {
-                self.unfocus();
-                return true;
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.editing_idx = None;
-                return true;
-            }
-            Key::Character(s) => {
-                for ch in s.chars() {
-                    if ch.is_ascii_digit() || ch == '.' || (ch == '-' && self.edit_buffer.is_empty()) {
-                        self.edit_buffer.push(ch);
-                    }
-                }
-                return true;
-            }
-            _ => {}
-        }
-        false
-    }
-
-    fn unfocus(&mut self) {
-        if let Some(i) = self.editing_idx.take() {
-            if let Ok(new_val) = self.edit_buffer.parse::<f32>() {
-                let range = self.maxs[i] - self.mins[i];
-                if range != 0.0 {
-                    self.values[i] = ((new_val - self.mins[i]) / range).clamp(0.0, 1.0);
-                } else {
-                    self.values[i] = 0.0;
-                }
-            }
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        
-        // Outline border box
-        let bx = self.base.x + 4.0;
-        let bw = self.base.w - 8.0;
-        let by = self.base.y + 20.0;
-        let bh = 84.0;
-        let border_color = [0.18, 0.18, 0.27, 1.0];
-        let border_t = 1.0;
-        
-        quads.push((bx, by, bw, border_t, border_color));
-        quads.push((bx, by + bh - border_t, bw, border_t, border_color));
-        quads.push((bx, by, border_t, bh, border_color));
-        quads.push((bx + bw - border_t, by, border_t, bh, border_color));
-        
-        let rects = self.get_row_rects();
-        for i in 0..3 {
-            let r = rects[i];
-            let track_x = self.base.x + 100.0;
-            let track_w = self.base.w - 188.0;
-            let track_y = r.1 + 4.0;
-            let track_h = 12.0;
-            
-            quads.push((track_x, track_y, track_w, track_h, colors::slider_track()));
-            
-            let thumb_size = track_h * 0.9;
-            let range = track_w - thumb_size;
-            let thumb_x = track_x + self.values[i] * range;
-            let thumb_color = if self.dragging_idx == Some(i) {
-                colors::SLIDER_THUMB_DRAG
-            } else {
-                colors::SLIDER_THUMB
-            };
-            quads.push((thumb_x, track_y + (track_h - thumb_size)/2.0, thumb_size, thumb_size, thumb_color));
-            
-            let rx = self.base.x + self.base.w - 68.0;
-            let bg_color = if self.editing_idx == Some(i) {
-                [0.06, 0.10, 0.18, 1.0]
-            } else {
-                [0.10, 0.10, 0.13, 1.0]
-            };
-            quads.push((rx, track_y, 60.0, track_h, bg_color));
-            
-            if self.editing_idx == Some(i) {
-                let border_color = [0.20, 0.50, 0.85, 1.0];
-                quads.push((rx, track_y, 60.0, border_t, border_color));
-                quads.push((rx, track_y + track_h - border_t, 60.0, border_t, border_color));
-                quads.push((rx, track_y, border_t, track_h, border_color));
-                quads.push((rx + 60.0 - border_t, track_y, border_t, track_h, border_color));
-            }
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        
-        if let Some(ref l) = self.base.label {
-            labels.push(TextLabel {
-                text: l.clone(),
-                x: self.base.x + 8.0,
-                y: self.base.y + 2.0,
-                font_size: 13.0,
-                color: [0xee, 0xee, 0xf0],
-            });
-        }
-        
-        let rects = self.get_row_rects();
-        for i in 0..3 {
-            let r = rects[i];
-            let track_y = r.1 + 4.0;
-            let ry = track_y;
-            
-            labels.push(TextLabel {
-                text: self.labels[i].clone(),
-                x: self.base.x + 16.0,
-                y: ry - 2.0,
-                font_size: 12.0,
-                color: [0xaa, 0xaa, 0xbb],
-            });
-            
-            let rx = self.base.x + self.base.w - 68.0;
-            let text = if self.editing_idx == Some(i) {
-                self.edit_buffer.clone()
-            } else {
-                let scaled_val = self.mins[i] + self.values[i] * (self.maxs[i] - self.mins[i]);
-                format!("{:.2}", scaled_val)
-            };
-            labels.push(TextLabel {
-                text,
-                x: rx + 8.0,
-                y: ry - 2.0,
-                font_size: 12.0,
-                color: [0xee, 0xee, 0xf0],
-            });
-        }
-        labels
-    }
-}
-
-
-pub struct ProgressBar {
-    base: WidgetBase,
-    value: f32,
-}
-
-impl ProgressBar {
-    pub fn new(value: f32) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            value,
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-}
-
-impl Widget for ProgressBar {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> { None }
-
-    fn color(&self) -> [f32; 4] { colors::PROGRESS_BG }
-}
-
-fn make_widget_text_buffer(fs: &mut glyphon::FontSystem, text: &str, size: f32, font_family: &str) -> glyphon::Buffer {
-    let metrics = glyphon::Metrics::new(size, size * 1.4);
-    let mut buf = glyphon::Buffer::new(fs, metrics);
-    let attrs = glyphon::Attrs::new().family(glyphon::Family::Name(font_family));
-    buf.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
-    buf.shape_until_scroll(fs, true);
-    buf
-}
-
-pub struct StatusBar {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    pub text: String,
-    pub text_buf: Option<glyphon::Buffer>,
-    pub text_offset_x: Option<f32>,
-    pub text_color: Option<[f32; 4]>,
-    pub bg_color: Option<[f32; 4]>,
-}
-
-impl StatusBar {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            hovered: false,
-            text: String::new(),
-            text_buf: None,
-            text_offset_x: None,
-            text_color: None,
-            bg_color: None,
-        }
-    }
-    pub fn with_text(mut self, text: &str) -> Self {
-        self.text = text.to_string();
-        self
-    }
-    pub fn with_text_offset_x(mut self, offset: f32) -> Self {
-        self.text_offset_x = Some(offset);
-        self
-    }
-    pub fn with_text_color(mut self, color: [f32; 4]) -> Self {
-        self.text_color = Some(color);
-        self
-    }
-    pub fn with_bg_color(mut self, color: [f32; 4]) -> Self {
-        self.bg_color = Some(color);
-        self
-    }
-    pub fn set_text_offset_x(&mut self, offset: f32) {
-        self.text_offset_x = Some(offset);
-    }
-    pub fn set_text_color(&mut self, color: [f32; 4]) {
-        self.text_color = Some(color);
-    }
-    pub fn set_bg_color(&mut self, color: [f32; 4]) {
-        self.bg_color = Some(color);
-    }
-}
-
-impl Widget for StatusBar {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { self.bg_color.unwrap_or(colors::STATUS_BG) }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn set_text(&mut self, text: &str) {
-        if self.text != text {
-            self.text = text.to_string();
-            self.text_buf = None;
-        }
-    }
-    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
-        if !self.text.is_empty() && self.text_buf.is_none() {
-            self.text_buf = Some(make_widget_text_buffer(fs, &self.text, 12.0, "Outfit"));
-        }
-    }
-    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
-        if let Some(ref text_buf) = self.text_buf {
-            let offset_x = self.text_offset_x.unwrap_or(12.0);
-            let color = self.text_color.map(|c| glyphon::Color::rgb(
-                (c[0] * 255.0) as u8,
-                (c[1] * 255.0) as u8,
-                (c[2] * 255.0) as u8,
-            )).unwrap_or_else(|| glyphon::Color::rgb(0xaa, 0xaa, 0xbb));
-            vec![(text_buf, self.x + offset_x, self.y + 4.0, color)]
-        } else {
-            Vec::new()
-        }
-    }
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.text.is_empty() {
-            let offset_x = self.text_offset_x.unwrap_or(12.0);
-            let color = self.text_color.map(|c| [
-                (c[0] * 255.0) as u8,
-                (c[1] * 255.0) as u8,
-                (c[2] * 255.0) as u8,
-            ]).unwrap_or([0xaa, 0xaa, 0xbb]);
-            vec![TextLabel {
-                text: self.text.clone(),
-                x: self.x + offset_x,
-                y: self.y + 4.0,
-                font_size: 12.0,
-                color,
-            }]
-        } else {
-            Vec::new()
-        }
-    }
-}
-
-pub struct Splitter {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    dragging: bool,
-    drag_ox: f32,
-}
-
-impl Splitter {
-    pub fn new(w: f32) -> Self {
-        Self { x: 0.0, y: 0.0, w, h: 0.0, hovered: false, dragging: false, drag_ox: 0.0 }
-    }
-}
-
-impl Widget for Splitter {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] {
-        if self.dragging { colors::SPLITTER_DRAG }
-        else if self.hovered { colors::SPLITTER_HOVER }
-        else { colors::SPLITTER_IDLE }
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was = self.hovered;
-        self.hovered = self.hit_test(px, py);
-        was != self.hovered
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                if self.hit_test(px, py) {
-                    self.drag_begin(px, py);
-                    return true;
-                }
-            }
-            ElementState::Released => {
-                if self.dragging { self.drag_end(); return true; }
-            }
-        }
-        false
-    }
-
-    fn is_dragging(&self) -> bool { self.dragging }
-    fn draggable(&self) -> bool { true }
-
-    fn drag_update(&mut self, px: f32, _py: f32) -> bool {
-        let new_x = px - self.drag_ox;
-        if (new_x - self.x).abs() > 0.5 {
-            self.x = new_x;
-            return true;
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, _py: f32) {
-        self.dragging = true;
-        self.drag_ox = px - self.x;
-    }
-
-    fn drag_end(&mut self) { self.dragging = false; }
-}
-
-#[derive(Debug, Clone)]
-pub struct Spinbox {
-    base: WidgetBase,
-    pub value: i32,
-    min: i32, max: i32, step: i32,
-    pub editing: bool,
-    pub edit_buffer: String,
-    pub cursor_idx: usize,
-    hover_dec: bool,
-    hover_inc: bool,
-    unit: Option<String>,
-    pub decimals: u32,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-}
-
-impl Spinbox {
-    pub fn new(value: i32, min: i32, max: i32, step: i32) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            value,
-            min,
-            max,
-            step,
-            editing: false,
-            edit_buffer: String::new(),
-            cursor_idx: 0,
-            hover_dec: false,
-            hover_inc: false,
-            unit: None,
-            decimals: 0,
-            parent: None,
-            children: Vec::new(),
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.base.label = Some(label.to_string());
-    }
-
-    pub fn with_unit(mut self, unit: &str) -> Self {
-        self.unit = Some(unit.to_string());
-        self
-    }
-
-    pub fn set_unit(&mut self, unit: &str) {
-        self.unit = Some(unit.to_string());
-    }
-
-    pub fn with_decimals(mut self, decimals: u32) -> Self {
-        self.decimals = decimals;
-        self
-    }
-}
-
-impl Widget for Spinbox {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] { colors::SPINBOX_BG }
-    fn value(&self) -> i32 { self.value }
-    fn widget_font(&self) -> Option<String> { Some("monospace".to_string()) }
-
-
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was = self.base.hovered;
-        self.base.hovered = self.hit_test(px, py);
-        if !self.base.hovered {
-            let changed = self.hover_dec || self.hover_inc;
-            self.hover_dec = false;
-            self.hover_inc = false;
-            return changed || was != self.base.hovered;
-        }
-        let split = self.base.x + self.base.w * 0.55;
-        let hd = px >= split && px < split + self.base.w * 0.225;
-        let hi = px >= split + self.base.w * 0.225;
-        let changed = hd != self.hover_dec || hi != self.hover_inc;
-        self.hover_dec = hd;
-        self.hover_inc = hi;
-        changed || was != self.base.hovered
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        if !self.hit_test(px, py) { return false; }
-        match state {
-            ElementState::Pressed => {
-                let split = self.base.x + self.base.w * 0.55;
-                if px >= split && px < split + self.base.w * 0.225 {
-                    self.value = (self.value - self.step).max(self.min);
-                    true
-                } else if px >= split + self.base.w * 0.225 {
-                    self.value = (self.value + self.step).min(self.max);
-                    true
-                } else if px < split {
-                    self.editing = true;
-                    if self.decimals > 0 {
-                        let divisor = 10.0f32.powi(self.decimals as i32);
-                        self.edit_buffer = format!("{:.width$}", self.value as f32 / divisor, width = self.decimals as usize);
-                    } else {
-                        self.edit_buffer = self.value.to_string();
-                    }
-                    let char_width = 8.4;
-                    let click_idx = (((px - (self.base.x + 4.0)) / char_width).round() as isize)
-                        .max(0)
-                        .min(self.edit_buffer.chars().count() as isize) as usize;
-                    self.cursor_idx = click_idx;
-                    focus::set_focused(self);
-                    true
-                } else {
-                    false
-                }
-            }
-            ElementState::Released => {
-                false
-            }
-        }
-    }
-
-    fn focus(&mut self) {
-        self.editing = true;
-        if self.decimals > 0 {
-            let divisor = 10.0f32.powi(self.decimals as i32);
-            self.edit_buffer = format!("{:.width$}", self.value as f32 / divisor, width = self.decimals as usize);
-        } else {
-            self.edit_buffer = self.value.to_string();
-        }
-        self.cursor_idx = self.edit_buffer.chars().count();
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        if self.editing {
-            self.editing = false;
-            if self.decimals > 0 {
-                if let Ok(val_f) = self.edit_buffer.parse::<f32>() {
-                    let divisor = 10.0f32.powi(self.decimals as i32);
-                    self.value = (val_f * divisor).round() as i32;
-                    self.value = self.value.clamp(self.min, self.max);
-                }
-            } else {
-                if let Ok(val) = self.edit_buffer.parse::<i32>() {
-                    self.value = val.clamp(self.min, self.max);
-                }
-            }
-        }
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !self.editing { return false; }
-        if event.state != ElementState::Pressed { return false; }
-        match &event.logical_key {
-            Key::Named(NamedKey::Backspace) => {
-                if self.cursor_idx > 0 {
-                    let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                    chars.remove(self.cursor_idx - 1);
-                    self.edit_buffer = chars.into_iter().collect();
-                    self.cursor_idx -= 1;
-                    return true;
-                }
-                false
-            }
-            Key::Named(NamedKey::Delete) => {
-                if self.cursor_idx < self.edit_buffer.chars().count() {
-                    let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                    chars.remove(self.cursor_idx);
-                    self.edit_buffer = chars.into_iter().collect();
-                    return true;
-                }
-                false
-            }
-            Key::Named(NamedKey::ArrowLeft) => {
-                if self.cursor_idx > 0 {
-                    self.cursor_idx -= 1;
-                    true
-                } else {
-                    false
-                }
-            }
-            Key::Named(NamedKey::ArrowRight) => {
-                if self.cursor_idx < self.edit_buffer.chars().count() {
-                    self.cursor_idx += 1;
-                    true
-                } else {
-                    false
-                }
-            }
-            Key::Named(NamedKey::Enter) => {
-                if self.decimals > 0 {
-                    if let Ok(val_f) = self.edit_buffer.parse::<f32>() {
-                        let divisor = 10.0f32.powi(self.decimals as i32);
-                        self.value = (val_f * divisor).round() as i32;
-                        self.value = self.value.clamp(self.min, self.max);
-                    }
-                } else {
-                    if let Ok(val) = self.edit_buffer.parse::<i32>() {
-                        self.value = val.clamp(self.min, self.max);
-                    }
-                }
-                self.editing = false;
-                true
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.editing = false;
-                true
-            }
-            _ => {
-                if let Some(text) = &event.text {
-                    for ch in text.chars() {
-                        match ch {
-                            '-' if self.cursor_idx == 0 && !self.edit_buffer.starts_with('-') => {
-                                self.edit_buffer.insert(0, '-');
-                                self.cursor_idx += 1;
-                            }
-                            '.' if self.decimals > 0 && !self.edit_buffer.contains('.') => {
-                                let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                                chars.insert(self.cursor_idx, '.');
-                                self.edit_buffer = chars.into_iter().collect();
-                                self.cursor_idx += 1;
-                            }
-                            '0'..='9' => {
-                                let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                                chars.insert(self.cursor_idx, ch);
-                                self.edit_buffer = chars.into_iter().collect();
-                                self.cursor_idx += 1;
-                            }
-                            _ => {}
-                        }
-                    }
-                }
-                true
-            }
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        let split = self.base.x + self.base.w * 0.55;
-        let btn_w = self.base.w * 0.225;
-        let inc_col = if self.hover_inc { colors::SPINBOX_BUTTON_HOVER } else { colors::SPINBOX_BUTTON };
-        let dec_col = if self.hover_dec { colors::SPINBOX_BUTTON_HOVER } else { colors::SPINBOX_BUTTON };
-        
-        let display_bg = if self.editing {
-            [0.06, 0.10, 0.18, 1.0] // Focused dark-blue input field look
-        } else {
-            colors::SPINBOX_DISPLAY
-        };
-        
-        quads.push((self.base.x, self.base.y, self.base.w * 0.55, self.base.h, display_bg));
-        quads.push((split, self.base.y, btn_w, self.base.h, dec_col));
-        quads.push((split + btn_w, self.base.y, btn_w, self.base.h, inc_col));
-        
-        if self.editing {
-            let border_color = [0.20, 0.50, 0.85, 1.0]; // Bright focused blue border
-            // Top border
-            quads.push((self.base.x, self.base.y, self.base.w * 0.55, 1.0, border_color));
-            // Bottom border
-            quads.push((self.base.x, self.base.y + self.base.h - 1.0, self.base.w * 0.55, 1.0, border_color));
-            // Left border
-            quads.push((self.base.x, self.base.y, 1.0, self.base.h, border_color));
-            // Right border
-            quads.push((self.base.x + self.base.w * 0.55 - 1.0, self.base.y, 1.0, self.base.h, border_color));
-
-            // Caret cursor
-            let char_width = 8.4;
-            let cursor_x = self.base.x + 4.0 + (self.cursor_idx as f32 * char_width);
-            let max_cursor_x = split - 4.0;
-            let final_cursor_x = cursor_x.min(max_cursor_x);
-            let cursor_y = self.base.y + (self.base.h - 14.0) / 2.0;
-            quads.push((final_cursor_x, cursor_y, 1.5, 14.0, [0.80, 0.80, 0.85, 1.0]));
-        }
-        
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x + 4.0,
-                y: self.base.y - 18.0,
-                font_size: 12.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-        let value_text = if self.editing {
-            self.edit_buffer.clone()
-        } else if self.decimals > 0 {
-            let divisor = 10.0f32.powi(self.decimals as i32);
-            format!("{:.width$}", self.value as f32 / divisor, width = self.decimals as usize)
-        } else {
-            self.value.to_string()
-        };
-        labels.push(TextLabel {
-            text: value_text,
-            x: self.base.x + 4.0,
-            y: self.base.y + (self.base.h - 14.0) / 2.0 - 2.0,
-            font_size: 14.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        if let Some(ref unit) = self.unit {
-            labels.push(TextLabel {
-                text: unit.clone(),
-                x: self.base.x + 4.0 + 36.0,
-                y: self.base.y + (self.base.h - 11.0) / 2.0 - 2.0,
-                font_size: 11.0,
-                color: [0x73, 0x73, 0x7a],
-            });
-        }
-        let split = self.base.x + self.base.w * 0.55;
-        labels.push(TextLabel {
-            text: "-".to_string(),
-            x: self.base.x + self.base.w * 0.6625 - 4.0,
-            y: self.base.y + (self.base.h - 12.0) / 2.0 - 2.0,
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        labels.push(TextLabel {
-            text: "+".to_string(),
-            x: self.base.x + self.base.w * 0.8875 - 4.0,
-            y: self.base.y + (self.base.h - 12.0) / 2.0 - 2.0,
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        labels
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-}
-
-impl Drop for Spinbox {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-#[derive(Debug)]
-pub struct ColorSelector {
-    base: WidgetBase,
-    pub color: [u8; 3],
-    just_clicked: bool,
-    pub editing: bool,
-    edit_buffer: String,
-    pub command: String,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-    child: std::sync::Arc<std::sync::Mutex<Option<std::process::Child>>>,
-}
-
-impl Clone for ColorSelector {
-    fn clone(&self) -> Self {
-        Self {
-            base: self.base.clone(),
-            color: self.color,
-            just_clicked: self.just_clicked,
-            editing: self.editing,
-            edit_buffer: self.edit_buffer.clone(),
-            command: self.command.clone(),
-            parent: self.parent,
-            children: self.children.clone(),
-            child: std::sync::Arc::new(std::sync::Mutex::new(None)),
-        }
-    }
-}
-
-impl ColorSelector {
-    pub fn new(color: [u8; 3]) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            color,
-            just_clicked: false,
-            editing: false,
-            edit_buffer: String::new(),
-            command: "clear-color-interface".to_string(),
-            parent: None,
-            children: Vec::new(),
-            child: std::sync::Arc::new(std::sync::Mutex::new(None)),
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn with_command(mut self, command: &str) -> Self {
-        self.command = command.to_string();
-        self
-    }
-}
-
-impl Widget for ColorSelector {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color_u8(&self) -> Option<[u8; 4]> {
-        Some([self.color[0], self.color[1], self.color[2], 255])
-    }
-
-
-
-    fn color(&self) -> [f32; 4] {
-        colors::to_linear([
-            self.color[0] as f32 / 255.0,
-            self.color[1] as f32 / 255.0,
-            self.color[2] as f32 / 255.0,
-            1.0,
-        ])
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        if state != ElementState::Pressed { return false; }
-        if !self.hit_test(px, py) { return false; }
-        if px >= self.base.x + self.base.w * 0.65 {
-            let hex = format!("#{:02x}{:02x}{:02x}", self.color[0], self.color[1], self.color[2]);
-            let mut child_guard = self.child.lock().unwrap();
-            if let Some(mut old_child) = child_guard.take() {
-                let _ = old_child.kill();
-            }
-            if let Ok(child) = std::process::Command::new(&self.command)
-                .arg(&hex)
-                .stdout(std::process::Stdio::piped())
-                .spawn()
-            {
-                *child_guard = Some(child);
-            }
-            return true;
-        }
-        self.focus();
-        true
-    }
-
-    fn take_click(&mut self) -> bool {
-        if self.just_clicked { self.just_clicked = false; true } else { false }
-    }
-
-    fn tick(&mut self, _dt: f32) -> bool {
-        let mut child_opt = self.child.lock().unwrap();
-        if let Some(ref mut child) = *child_opt {
-            match child.try_wait() {
-                Ok(Some(_status)) => {
-                    let child = child_opt.take().unwrap();
-                    if let Ok(output) = child.wait_with_output() {
-                        let stdout_str = String::from_utf8_lossy(&output.stdout);
-                        for line in stdout_str.lines().rev() {
-                            if let Some(c) = parse_hex(line.trim()) {
-                                self.color = c;
-                                self.just_clicked = true;
-                                return true;
-                            }
-                        }
-                    }
-                }
-                Ok(None) => {}
-                Err(e) => {
-                    eprintln!("Error checking color selector child process: {:?}", e);
-                    *child_opt = None;
-                }
-            }
-        }
-        false
-    }
-
-    fn focus(&mut self) {
-        self.editing = true;
-        self.edit_buffer = format!("#{:02x}{:02x}{:02x}", self.color[0], self.color[1], self.color[2]);
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        if self.editing {
-            self.editing = false;
-            if let Some(c) = parse_hex(&self.edit_buffer) {
-                self.color = c;
-            }
-        }
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !self.editing { return false; }
-        if event.state != ElementState::Pressed { return false; }
-        match &event.logical_key {
-            Key::Named(NamedKey::Backspace) => {
-                self.edit_buffer.pop();
-                true
-            }
-            Key::Named(NamedKey::Enter) => {
-                if let Some(c) = parse_hex(&self.edit_buffer) {
-                    self.color = c;
-                }
-                self.editing = false;
-                true
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.editing = false;
-                true
-            }
-            _ => {
-                if let Some(text) = &event.text {
-                    for ch in text.chars() {
-                        match ch {
-                            '#' if self.edit_buffer.is_empty() => self.edit_buffer.push('#'),
-                            '0'..='9' | 'a'..='f' | 'A'..='F' => {
-                                if self.edit_buffer.len() < 7 { self.edit_buffer.push(ch.to_ascii_lowercase()); }
-                            }
-                            _ => {}
-                        }
-                    }
-                }
-                true
-            }
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        let pick_x = self.base.x + self.base.w * 0.65;
-        let pick_w = self.base.w * 0.35;
-
-        // Draw the text box part background and border
-        let bg_color = if self.editing {
-            [0.06, 0.10, 0.18, 1.0]
-        } else {
-            [0.08, 0.08, 0.12, 1.0]
-        };
-        let border_color = if self.editing {
-            [0.20, 0.50, 0.85, 1.0]
-        } else if self.base.hovered {
-            [0.25, 0.25, 0.35, 1.0]
-        } else {
-            [0.18, 0.18, 0.24, 1.0]
-        };
-
-        // Background & border for hex text box part
-        quads.push((self.base.x, self.base.y, self.base.w * 0.65, self.base.h, border_color));
-        quads.push((self.base.x + 1.0, self.base.y + 1.0, self.base.w * 0.65 - 2.0, self.base.h - 2.0, bg_color));
-
-        let linear_c = colors::to_linear([
-            self.color[0] as f32 / 255.0,
-            self.color[1] as f32 / 255.0,
-            self.color[2] as f32 / 255.0,
-            1.0,
-        ]);
-        let border_w = 1.0;
-        let border_c = colors::color_borders_color();
-        
-        let r = border_c[0];
-        let g = border_c[1];
-        let b = border_c[2];
-        let steps = 6;
-        for i in (1..=steps).rev() {
-            let offset = i as f32 * 0.75;
-            let rx = pick_x - offset;
-            let ry = self.base.y - offset;
-            let rw = pick_w + 2.0 * offset;
-            let rh = self.base.h + 2.0 * offset;
-            let alpha = 0.08 * (1.0 - (i as f32 / steps as f32).powf(1.5));
-            if alpha > 0.001 {
-                quads.push((rx, ry, rw, rh, [r, g, b, alpha]));
-            }
-        }
-
-        quads.push((pick_x, self.base.y, pick_w, self.base.h, border_c));
-        quads.push((
-            pick_x + border_w,
-            self.base.y + border_w,
-            pick_w - 2.0 * border_w,
-            self.base.h - 2.0 * border_w,
-            linear_c,
-        ));
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x + 4.0,
-                y: self.base.y - 18.0,
-                font_size: 12.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-        let hex = if self.editing { self.edit_buffer.clone() } else { format!("#{:02x}{:02x}{:02x}", self.color[0], self.color[1], self.color[2]) };
-        labels.push(TextLabel {
-            text: hex,
-            x: self.base.x + 4.0,
-            y: self.base.y + 3.0,
-            font_size: 12.0,
-            color: [0xcc, 0xcc, 0xd4],
-        });
-        labels
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-}
-
-impl Drop for ColorSelector {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-const BREADCRUMB_PADDING: f32 = 8.0;
-const SEGMENT_GAP: f32 = 4.0;
-
-#[derive(Debug, Clone)]
-pub struct Breadcrumb {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    path: Vec<String>,
-    hovered_seg: Option<usize>,
-    clicked_seg: Option<usize>,
-    pub network_opacity: f32,
-}
-
-impl Breadcrumb {
-    pub fn new() -> Self {
-        Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false,
-               path: Vec::new(), hovered_seg: None, clicked_seg: None, network_opacity: 1.0 }
-    }
-
-    fn seg_at(&self, px: f32) -> Option<usize> {
-        let mut cx = self.x + BREADCRUMB_PADDING;
-        for (i, seg) in self.path.iter().enumerate() {
-            let w = seg.len() as f32 * 7.5;
-            if px >= cx && px < cx + w {
-                return Some(i);
-            }
-            cx += w + SEGMENT_GAP;
-        }
-        None
-    }
-}
-
-impl Widget for Breadcrumb {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn set_network_opacity(&mut self, opacity: f32) { self.network_opacity = opacity; }
-    fn color(&self) -> [f32; 4] { [0.10, 0.10, 0.14, self.network_opacity] }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was = self.hovered;
-        self.hovered = self.hit_test(px, py);
-        let old = self.hovered_seg;
-        self.hovered_seg = if self.hovered { self.seg_at(px) } else { None };
-        was != self.hovered || old != self.hovered_seg
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, _py: f32) -> bool {
-        if button != MouseButton::Left || state != ElementState::Pressed { return false; }
-        if let Some(i) = self.seg_at(px) {
-            if i < self.path.len() - 1 {
-                self.clicked_seg = Some(i);
-                return true;
-            }
-        }
-        false
-    }
-
-    fn set_path(&mut self, segments: &[String]) {
-        let mut s = Vec::with_capacity(segments.len().max(1));
-        if segments.is_empty() || (segments.len() == 1 && segments[0].is_empty()) {
-            s.push("/".to_string());
-        } else {
-            s.push("/".to_string());
-            for name in segments {
-                s.push(format!(" \u{203A} {}", name));
-            }
-        }
-        self.path = s;
-    }
-
-    fn path_click(&mut self) -> Option<usize> { self.clicked_seg.take() }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if let Some(i) = self.hovered_seg {
-            let mut cx = self.x + BREADCRUMB_PADDING;
-            for j in 0..i {
-                let w = self.path[j].len() as f32 * 7.5;
-                cx += w + SEGMENT_GAP;
-            }
-            let w = self.path[i].len() as f32 * 7.5;
-            quads.push((cx, self.y, w, self.h, [1.0, 1.0, 1.0, 0.06]));
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        let mut cx = self.x + BREADCRUMB_PADDING;
-        for (i, seg) in self.path.iter().enumerate() {
-            labels.push(TextLabel {
-                text: seg.clone(),
-                x: cx,
-                y: self.y + 6.0,
-                font_size: 12.0,
-                color: if i == self.path.len() - 1 { [0xcc, 0xcc, 0xd4] } else { [0x88, 0x88, 0x99] },
-            });
-            cx += seg.len() as f32 * 7.5 + SEGMENT_GAP;
-        }
-        labels
-    }
-}
-
-fn parse_hex(s: &str) -> Option<[u8; 3]> {
-    let s = s.trim_start_matches('#');
-    if s.len() != 6 { return None; }
-    let r = u8::from_str_radix(&s[0..2], 16).ok()?;
-    let g = u8::from_str_radix(&s[2..4], 16).ok()?;
-    let b = u8::from_str_radix(&s[4..6], 16).ok()?;
-    Some([r, g, b])
-}
-
-pub struct Spreadsheet {
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
-    hovered: bool,
-    visible: bool,
-    headers: Vec<String>,
-    rows: Vec<Vec<String>>,
-    scroll_y: f32,
-    scroll_velocity: f32,
-    dragging_scrollbar: bool,
-    drag_offset_y: f32,
-    scrollbar_hovered: bool,
-    scrollbar_thumb_hovered: bool,
-}
-
-impl Spreadsheet {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            hovered: false,
-            visible: false,
-            headers: Vec::new(),
-            rows: Vec::new(),
-            scroll_y: 0.0,
-            scroll_velocity: 0.0,
-            dragging_scrollbar: false,
-            drag_offset_y: 0.0,
-            scrollbar_hovered: false,
-            scrollbar_thumb_hovered: false,
-        }
-    }
-}
-
-impl Widget for Spreadsheet {
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        if !self.visible {
-            (0.0, 0.0, 0.0, 0.0)
-        } else {
-            (self.x, self.y, self.w, self.h)
-        }
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.x = x;
-        self.y = y;
-        self.w = w;
-        self.h = h;
-    }
-
-    fn color(&self) -> [f32; 4] {
-        if !self.visible {
-            [0.0, 0.0, 0.0, 0.0]
-        } else {
-            colors::PARAM_BG
-        }
-    }
-
-    fn set_hovered(&mut self, v: bool) {
-        self.hovered = v;
-    }
-
-    fn hovered(&self) -> bool {
-        self.hovered
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        px >= rx && px <= rx + rw && py >= ry && py <= ry + rh
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-    }
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn set_spreadsheet_data(&mut self, headers: Vec<String>, rows: Vec<Vec<String>>) {
-        self.headers = headers;
-        self.rows = rows;
-        
-        // Clamp scroll_y to new bounds
-        let content_h = self.rows.len() as f32 * 24.0;
-        let visible_h = (self.h - 24.0).max(0.0);
-        let max_scroll_y = (content_h - visible_h).max(0.0);
-        self.scroll_y = self.scroll_y.clamp(0.0, max_scroll_y);
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was_hovered = self.hovered;
-        self.hovered = self.hit_test(px, py);
-
-        let was_sb_hovered = self.scrollbar_hovered;
-        let was_thumb_hovered = self.scrollbar_thumb_hovered;
-
-        let content_h = self.rows.len() as f32 * 24.0;
-        let visible_h = (self.h - 24.0).max(0.0);
-        if visible_h > 0.0 && content_h > visible_h {
-            let scrollbar_w = 6.0;
-            let scrollbar_padding = 2.0;
-            let scrollbar_x = self.x + self.w - scrollbar_w - scrollbar_padding;
-            let track_y = self.y + 24.0;
-
-            self.scrollbar_hovered = px >= scrollbar_x - 2.0 && px <= self.x + self.w
-                && py >= track_y && py <= self.y + self.h;
-
-            let thumb_h = ((visible_h / content_h) * visible_h).clamp(15.0_f32.min(visible_h), visible_h);
-            let max_scroll_y = content_h - visible_h;
-            let scroll_ratio = self.scroll_y / max_scroll_y;
-            let track_scroll_range = visible_h - thumb_h;
-            let thumb_y = track_y + scroll_ratio * track_scroll_range;
-
-            self.scrollbar_thumb_hovered = px >= scrollbar_x - 2.0 && px <= self.x + self.w
-                && py >= thumb_y && py <= thumb_y + thumb_h;
-        } else {
-            self.scrollbar_hovered = false;
-            self.scrollbar_thumb_hovered = false;
-        }
-
-        was_hovered != self.hovered
-            || was_sb_hovered != self.scrollbar_hovered
-            || was_thumb_hovered != self.scrollbar_thumb_hovered
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if self.hit_test(px, py) {
-            let content_h = self.rows.len() as f32 * 24.0;
-            let visible_h = (self.h - 24.0).max(0.0);
-            if visible_h > 0.0 && content_h > visible_h {
-                let scroll_amount = match delta {
-                    MouseScrollDelta::LineDelta(_x, y) => *y * 24.0,
-                    MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
-                };
-                self.scroll_velocity += scroll_amount * 12.0;
-                return true;
-            }
-        }
-        false
-    }
-
-    fn draggable(&self) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let content_h = self.rows.len() as f32 * 24.0;
-        let visible_h = (self.h - 24.0).max(0.0);
-        visible_h > 0.0 && content_h > visible_h
-    }
-
-    fn is_dragging(&self) -> bool {
-        self.dragging_scrollbar
-    }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        self.scroll_velocity = 0.0;
-        let content_h = self.rows.len() as f32 * 24.0;
-        let visible_h = (self.h - 24.0).max(0.0);
-        if visible_h > 0.0 && content_h > visible_h {
-            let scrollbar_w = 6.0;
-            let scrollbar_padding = 2.0;
-            let scrollbar_x = self.x + self.w - scrollbar_w - scrollbar_padding;
-            let track_y = self.y + 24.0;
-
-            if px >= scrollbar_x - 4.0 && px <= self.x + self.w
-                && py >= track_y && py <= self.y + self.h
-            {
-                self.dragging_scrollbar = true;
-
-                let thumb_h = ((visible_h / content_h) * visible_h).clamp(15.0_f32.min(visible_h), visible_h);
-                let max_scroll_y = content_h - visible_h;
-                let scroll_ratio = self.scroll_y / max_scroll_y;
-                let track_scroll_range = visible_h - thumb_h;
-                let thumb_y = track_y + scroll_ratio * track_scroll_range;
-
-                if py >= thumb_y && py <= thumb_y + thumb_h {
-                    self.drag_offset_y = py - thumb_y;
-                } else {
-                    self.drag_offset_y = thumb_h / 2.0;
-                    let new_thumb_y = py - self.drag_offset_y;
-                    let scroll_ratio = if track_scroll_range > 0.0 {
-                        ((new_thumb_y - track_y) / track_scroll_range).clamp(0.0, 1.0)
-                    } else {
-                        0.0
-                    };
-                    self.scroll_y = scroll_ratio * max_scroll_y;
-                }
-            }
-        }
-    }
-
-    fn drag_update(&mut self, _px: f32, py: f32) -> bool {
-        if self.dragging_scrollbar {
-            self.scroll_velocity = 0.0;
-            let content_h = self.rows.len() as f32 * 24.0;
-            let visible_h = (self.h - 24.0).max(0.0);
-            if visible_h > 0.0 && content_h > visible_h {
-                let thumb_h = ((visible_h / content_h) * visible_h).clamp(15.0_f32.min(visible_h), visible_h);
-                let max_scroll_y = content_h - visible_h;
-                let track_y = self.y + 24.0;
-                let track_scroll_range = visible_h - thumb_h;
-
-                let new_thumb_y = py - self.drag_offset_y;
-                let scroll_ratio = if track_scroll_range > 0.0 {
-                    ((new_thumb_y - track_y) / track_scroll_range).clamp(0.0, 1.0)
-                } else {
-                    0.0
-                };
-                let old_scroll_y = self.scroll_y;
-                self.scroll_y = scroll_ratio * max_scroll_y;
-
-                return (self.scroll_y - old_scroll_y).abs() > 0.01;
-            }
-        }
-        false
-    }
-
-    fn drag_end(&mut self) {
-        self.dragging_scrollbar = false;
-        self.scroll_velocity = 0.0;
-    }
-
-    fn tick(&mut self, dt: f32) -> bool {
-        if self.scroll_velocity.abs() > 0.01 {
-            let content_h = self.rows.len() as f32 * 24.0;
-            let visible_h = (self.h - 24.0).max(0.0);
-            let max_scroll_y = (content_h - visible_h).max(0.0);
-            let old_scroll_y = self.scroll_y;
-
-            self.scroll_y = (self.scroll_y + self.scroll_velocity * dt).clamp(0.0, max_scroll_y);
-
-            // Decelerate with friction (exponential decay)
-            let friction = 8.0;
-            self.scroll_velocity *= (-friction * dt).exp();
-
-            if self.scroll_y == 0.0 || self.scroll_y == max_scroll_y {
-                self.scroll_velocity = 0.0;
-            }
-
-            if self.scroll_velocity.abs() < 5.0 {
-                self.scroll_velocity = 0.0;
-            }
-
-            (self.scroll_y - old_scroll_y).abs() > 0.01
-        } else {
-            false
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        
-        // Header bg
-        quads.push((self.x, self.y, self.w, 24.0, [0.12, 0.12, 0.16, 0.4]));
-
-        // Zebra rows
-        let row_h = 24.0;
-        let body_top = self.y + 24.0;
-        let body_bottom = self.y + self.h;
-        for i in 0..self.rows.len() {
-            let ry = self.y + 24.0 + i as f32 * row_h - self.scroll_y;
-            if ry + row_h <= body_top || ry >= body_bottom {
-                continue;
-            }
-            let draw_y = ry.max(body_top);
-            let draw_h = (ry + row_h).min(body_bottom) - draw_y;
-            if draw_h > 0.0 {
-                let row_color = if i % 2 == 0 {
-                    [0.10, 0.10, 0.13, 0.15]
-                } else {
-                    [0.08, 0.08, 0.11, 0.05]
-                };
-                quads.push((self.x, draw_y, self.w, draw_h, row_color));
-
-                // Horizontal row separator
-                let sep_y = ry + row_h;
-                if sep_y >= body_top && sep_y < body_bottom {
-                    quads.push((self.x, sep_y, self.w, 1.0, [0.20, 0.20, 0.25, 0.15]));
-                }
-            }
-        }
-
-        // Header separator
-        quads.push((self.x, self.y + 24.0, self.w, 1.0, [0.20, 0.20, 0.25, 0.25]));
-
-        // Vertical separators
-        let divider_h = self.h;
-        if divider_h > 0.0 && !self.headers.is_empty() {
-            let n_cols = self.headers.len();
-            for i in 1..n_cols {
-                let r = i as f32 / n_cols as f32;
-                quads.push((self.x + self.w * r, self.y, 1.0, divider_h, [0.20, 0.20, 0.25, 0.15]));
-            }
-        }
-
-        // Scrollbar track & thumb
-        let content_h = self.rows.len() as f32 * row_h;
-        let visible_h = (self.h - 24.0).max(0.0);
-        if visible_h > 0.0 && content_h > visible_h {
-            let scrollbar_w = 6.0;
-            let scrollbar_padding = 2.0;
-            let scrollbar_x = self.x + self.w - scrollbar_w - scrollbar_padding;
-            let track_y = self.y + 24.0;
-            let track_h = visible_h;
-
-            // Track BG
-            quads.push((scrollbar_x, track_y, scrollbar_w, track_h, [0.05, 0.05, 0.08, 0.15]));
-
-            // Thumb
-            let thumb_h = ((visible_h / content_h) * visible_h).clamp(15.0_f32.min(visible_h), visible_h);
-            let max_scroll_y = content_h - visible_h;
-            let scroll_ratio = self.scroll_y / max_scroll_y;
-            let track_scroll_range = visible_h - thumb_h;
-            let thumb_y = track_y + scroll_ratio * track_scroll_range;
-
-            let thumb_color = if self.dragging_scrollbar {
-                [0.40, 0.40, 0.48, 1.0]
-            } else if self.scrollbar_thumb_hovered {
-                [0.32, 0.32, 0.38, 1.0]
-            } else if self.scrollbar_hovered {
-                [0.24, 0.24, 0.30, 0.9]
-            } else {
-                [0.18, 0.18, 0.24, 0.7]
-            };
-
-            quads.push((scrollbar_x, thumb_y, scrollbar_w, thumb_h, thumb_color));
-        }
-
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        if self.headers.is_empty() {
-            return labels;
-        }
-
-        let n_cols = self.headers.len();
-        for (i, header) in self.headers.iter().enumerate() {
-            let cx = self.x + self.w * (i as f32 / n_cols as f32) + 8.0;
-            labels.push(TextLabel {
-                text: header.clone(),
-                x: cx,
-                y: self.y + 6.0,
-                font_size: 12.0,
-                color: [0xdd, 0xdd, 0xee],
-            });
-        }
-
-        let row_h = 24.0;
-        let body_top = self.y + 24.0;
-        let body_bottom = self.y + self.h;
-        for (i, row) in self.rows.iter().enumerate() {
-            let ry = self.y + 24.0 + i as f32 * row_h - self.scroll_y;
-            // Only show text if the row is fully inside the spreadsheet body
-            if ry < body_top || ry + row_h > body_bottom {
-                continue;
-            }
-
-            for (col_idx, val) in row.iter().enumerate().take(n_cols) {
-                let cx = self.x + self.w * (col_idx as f32 / n_cols as f32) + 8.0;
-                labels.push(TextLabel {
-                    text: val.clone(),
-                    x: cx,
-                    y: ry + 6.0,
-                    font_size: 12.0,
-                    color: [0xbb, 0xbb, 0xcc],
-                });
-            }
-        }
-        labels
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct ScrollBox {
-    x: f32, y: f32, w: f32, h: f32,
-    pub scroll_y: f32,
-    pub content_h: f32,
-    pub viewport_y: f32,
-    pub viewport_h: f32,
-    hovered: bool,
-    pub show_border: bool,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-}
-
-impl ScrollBox {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0, y: 0.0, w: 0.0, h: 0.0,
-            scroll_y: 0.0,
-            content_h: 0.0,
-            viewport_y: 0.0,
-            viewport_h: 0.0,
-            hovered: false,
-            show_border: true,
-            parent: None,
-            children: Vec::new(),
-        }
-    }
-
-    pub fn update_bounds(&mut self, content_h: f32, viewport_y: f32, viewport_h: f32) {
-        self.content_h = content_h;
-        self.viewport_y = viewport_y;
-        self.viewport_h = viewport_h;
-        let max_scroll = (content_h - viewport_h).max(0.0);
-        self.scroll_y = self.scroll_y.clamp(0.0, max_scroll);
-    }
-
-    pub fn get_item_draw_y(&self, virtual_y: f32, item_h: f32) -> Option<f32> {
-        let draw_y = self.viewport_y + virtual_y - self.scroll_y;
-        if draw_y >= self.viewport_y - 1.0 && draw_y + item_h <= self.viewport_y + self.viewport_h + 1.0 {
-            Some(draw_y)
-        } else {
-            None
-        }
-    }
-}
-
-impl Widget for ScrollBox {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn color(&self) -> [f32; 4] { [0.08, 0.08, 0.12, 0.3] }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn highlight_color(&self) -> Option<[f32; 4]> { None }
-
-    fn focus(&mut self) {
-        focus::set_focused(self);
-    }
-    fn unfocus(&mut self) {}
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button == MouseButton::Left && state == ElementState::Pressed {
-            if self.hit_test(px, py) {
-                self.focus();
-                return true;
-            }
-        }
-        false
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was = self.hovered;
-        self.hovered = self.hit_test(px, py);
-        was != self.hovered
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if self.hit_test(px, py) {
-            let scroll_speed = 24.0;
-            let dy = match delta {
-                MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
-                MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
-            };
-            let old_scroll = self.scroll_y;
-            let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-            self.scroll_y = (self.scroll_y + dy).clamp(0.0, max_scroll);
-            (self.scroll_y - old_scroll).abs() > 0.01
-        } else {
-            false
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        
-        // Background
-        quads.push((self.x, self.y, self.w, self.h, [0.08, 0.08, 0.12, 0.3]));
-
-        // Border lines
-        let box_border_color = if focus::is_focused(self) {
-            [0.30, 0.50, 0.32, 1.0] // Focused green
-        } else if self.hovered {
-            [0.25, 0.25, 0.35, 1.0] // Hovered
-        } else {
-            [0.18, 0.18, 0.24, 1.0] // Default
-        };
-        quads.push((self.x, self.y, self.w, 1.0, box_border_color)); // Top
-        quads.push((self.x, self.y + self.h - 1.0, self.w, 1.0, box_border_color)); // Bottom
-        quads.push((self.x, self.y, 1.0, self.h, box_border_color)); // Left
-        quads.push((self.x + self.w - 1.0, self.y, 1.0, self.h, box_border_color)); // Right
-
-        // Scrollbar
-        if self.content_h > self.viewport_h {
-            let sb_x = self.x + self.w - 8.0;
-            let sb_w = 4.0;
-            let sb_track_h = self.viewport_h - 8.0;
-            let sb_track_y = self.viewport_y + 4.0;
-
-            // Track
-            quads.push((sb_x, sb_track_y, sb_w, sb_track_h, [0.15, 0.15, 0.20, 0.3]));
-
-            // Thumb
-            let visible_ratio = self.viewport_h / self.content_h;
-            let thumb_h = (sb_track_h * visible_ratio).clamp(20.0, sb_track_h);
-            let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-            let scroll_ratio = if max_scroll > 0.0 { self.scroll_y / max_scroll } else { 0.0 };
-            let thumb_y = sb_track_y + scroll_ratio * (sb_track_h - thumb_h);
-
-            quads.push((sb_x, thumb_y, sb_w, thumb_h, [0.60, 0.60, 0.65, 0.4]));
-        }
-
-        quads
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !focus::is_focused(self) {
-            return false;
-        }
-        if event.state != ElementState::Pressed {
-            return false;
-        }
-        if event.ctrl {
-            match &event.logical_key {
-                Key::Character(c) if c == "n" || c == "N" => {
-                    let old_scroll = self.scroll_y;
-                    let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-                    self.scroll_y = (self.scroll_y + 24.0).clamp(0.0, max_scroll);
-                    (self.scroll_y - old_scroll).abs() > 0.01
-                }
-                Key::Character(c) if c == "p" || c == "P" => {
-                    let old_scroll = self.scroll_y;
-                    let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-                    self.scroll_y = (self.scroll_y - 24.0).clamp(0.0, max_scroll);
-                    (self.scroll_y - old_scroll).abs() > 0.01
-                }
-                _ => false,
-            }
-        } else {
-            match &event.logical_key {
-                Key::Named(NamedKey::ArrowDown) => {
-                    let old_scroll = self.scroll_y;
-                    let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-                    self.scroll_y = (self.scroll_y + 24.0).clamp(0.0, max_scroll);
-                    (self.scroll_y - old_scroll).abs() > 0.01
-                }
-                Key::Named(NamedKey::ArrowUp) => {
-                    let old_scroll = self.scroll_y;
-                    let max_scroll = (self.content_h - self.viewport_h).max(0.0);
-                    self.scroll_y = (self.scroll_y - 24.0).clamp(0.0, max_scroll);
-                    (self.scroll_y - old_scroll).abs() > 0.01
-                }
-                _ => false,
-            }
-        }
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-}
-
-impl Drop for ScrollBox {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_rangeslider_interaction() {
-        let mut rs = RangeSlider::new();
-        rs.set_rect(10.0, 10.0, 200.0, 20.0);
-
-        // Low value: 0.2, High value: 0.8
-        let (low, high) = rs.values();
-        assert_eq!(low, 0.2);
-        assert_eq!(high, 0.8);
-
-        // Thumb size = h * 0.9 = 18.0
-        // Range = w - thumb_size = 200.0 - 18.0 = 182.0
-        // Thumb low center: x + 0.2 * 182.0 + 9.0 = 10.0 + 36.4 + 9.0 = 55.4
-        // Thumb high center: x + 0.8 * 182.0 + 9.0 = 10.0 + 145.6 + 9.0 = 164.6
-
-        // 1. Drag Low thumb from 0.2 to 0.45
-        // Click at px = 55.4 (center of low thumb)
-        rs.drag_begin(55.4, 20.0);
-        assert_eq!(rs.active_thumb, Some(ActiveThumb::Low));
-
-        // Drag to px = 100.9 (new low value = (100.9 - offset(9.0) - 10.0) / 182.0 = 81.9 / 182.0 = 0.45)
-        let changed = rs.drag_update(100.9, 20.0);
-        assert!(changed);
-        assert!((rs.values().0 - 0.45).abs() < 0.01);
-        assert_eq!(rs.values().1, 0.8); // High value unchanged
-
-        rs.drag_end();
-        assert_eq!(rs.active_thumb, None);
-
-        // 2. Drag High thumb from 0.8 to 0.6
-        // Click at px = 164.6 (center of high thumb)
-        rs.drag_begin(164.6, 20.0);
-        assert_eq!(rs.active_thumb, Some(ActiveThumb::High));
-
-        // Drag to px = 128.2 (new high value = (128.2 - offset(9.0) - 10.0) / 182.0 = 109.2 / 182.0 = 0.6)
-        let changed = rs.drag_update(128.2, 20.0);
-        assert!(changed);
-        assert!((rs.values().1 - 0.6).abs() < 0.01);
-
-        rs.drag_end();
-    }
-
-    #[test]
-    fn test_rangeslider_overlap() {
-        let mut rs = RangeSlider::new().with_values(0.5, 0.5);
-        rs.set_rect(10.0, 10.0, 200.0, 20.0);
-
-        // Both low and high are 0.5. Thumb center = 10.0 + 0.5 * 182.0 + 9.0 = 110.0
-        // Click to the left of center should select Low thumb
-        rs.drag_begin(109.0, 20.0);
-        assert_eq!(rs.active_thumb, Some(ActiveThumb::Low));
-        rs.drag_end();
-
-        // Click to the right of center should select High thumb
-        rs.drag_begin(111.0, 20.0);
-        assert_eq!(rs.active_thumb, Some(ActiveThumb::High));
-        rs.drag_end();
-
-        // Drag Low thumb past High value (0.5). It should be constrained to 0.5
-        rs.drag_begin(110.0, 20.0); // selects low
-        rs.drag_update(150.0, 20.0); // drag past high
-        assert_eq!(rs.values().0, 0.5); // constrained
-        rs.drag_end();
-    }
-
-
-    #[test]
-    fn test_node_toggle_geometry_visibility() {
-        let mut node = Node::new(100.0, 100.0, 200.0, 50.0, "Test Node");
-
-        // 1. Initial state
-        assert!(node.geom_visible());
-        assert!(!node.take_geom_toggle());
-        assert!(node.draggable());
-
-        // Get the toggle rect
-        let (tx, ty, tw, th) = node.toggle_rect();
-        
-        // 2. Hover toggle area
-        // Move cursor inside toggle area
-        let changed = node.cursor_moved(tx + tw / 2.0, ty + th / 2.0);
-        assert!(changed);
-        assert!(node.toggle_hovered);
-        assert!(!node.draggable(), "Node should not be draggable when hovering over the toggle widget");
-
-        // Move cursor outside toggle area but inside node
-        let changed2 = node.cursor_moved(tx - 10.0, ty + th / 2.0);
-        assert!(changed2);
-        assert!(!node.toggle_hovered);
-        assert!(node.draggable());
-
-        // 3. Click toggle area
-        // Move cursor back inside toggle area
-        node.cursor_moved(tx + tw / 2.0, ty + th / 2.0);
-        // Press Left button
-        let input_changed = node.mouse_input(MouseButton::Left, ElementState::Pressed, tx + tw / 2.0, ty + th / 2.0);
-        assert!(input_changed);
-        assert!(!node.geom_visible(), "Geometry visibility should be toggled off");
-        assert!(node.take_geom_toggle(), "take_geom_toggle should return true after toggle click");
-        assert!(!node.take_geom_toggle(), "take_geom_toggle should clear state after being called once");
-
-        // Click again to toggle back on
-        let input_changed2 = node.mouse_input(MouseButton::Left, ElementState::Pressed, tx + tw / 2.0, ty + th / 2.0);
-        assert!(input_changed2);
-        assert!(node.geom_visible(), "Geometry visibility should be toggled back on");
-        assert!(node.take_geom_toggle());
-    }
-
-    #[test]
-    fn test_graph_interaction() {
-        let mut graph = Graph::new();
-        graph.set_rect(0.0, 0.0, 800.0, 600.0);
-        graph.set_grid_sizes(100.0, 50.0);
-        graph.set_skipped_sizes(10.0, 20.0);
-        graph.set_grid_origin(0.0, 0.0);
-
-        let nodes = vec![
-            GraphNode {
-                name: "Node A".to_string(),
-                position: (0.0, 0.0),
-                parameters: vec![],
-                geom_visible: true,
-            },
-            GraphNode {
-                name: "Node B".to_string(),
-                position: (2.0, 1.0),
-                parameters: vec![],
-                geom_visible: true,
-            },
-        ];
-        graph.set_nodes(&nodes);
-
-        // 1. Initial State
-        assert_eq!(graph.get_nodes().len(), 2);
-        assert_eq!(graph.selected_node(), None);
-
-        // 2. Select Node A
-        // Node A screen rect: (0, 0, 100, 50)
-        let clicked = graph.mouse_input(MouseButton::Left, ElementState::Pressed, 50.0, 25.0);
-        assert!(clicked);
-        assert_eq!(graph.selected_node(), Some(0));
-        assert!(graph.draggable());
-
-        // 3. Drag Node A
-        graph.drag_begin(50.0, 25.0);
-        assert!(graph.is_dragging());
-
-        // Enable snapping
-        graph.set_grid_snap_enabled(true);
-        graph.drag_update(170.0, 85.0); // drag offset from Node A center (50, 25): nx = 170 - 50 = 120, ny = 85 - 25 = 60
-        assert_eq!(graph.drag_node_pos, Some((120.0, 60.0)));
-
-        graph.drag_end();
-        assert_eq!(graph.get_nodes()[0].position, (1.0, 1.0)); // Snapped grid position: (120/120, 60/60)
-
-        // 4. Toggle geometry visibility of Node B
-        // Node B screen rect: (2 * 120 = 240, 1 * 60 = 60, 100, 50)
-        // Toggle button: tx = 240 + 100 - 30 = 310, ty = 60 + (50 - 18)/2 = 76, tw = 18, th = 18
-        let clicked_toggle = graph.mouse_input(MouseButton::Left, ElementState::Pressed, 319.0, 85.0);
-        assert!(clicked_toggle);
-        assert_eq!(graph.take_node_geom_toggle(), Some((1, false)));
-    }
-
-
-    #[test]
-    fn test_menubar_vertical_horizontal_labels() {
-        // Create MenuBar with custom horizontal and vertical labels
-        let mut menubar = MenuBar::new(0.0, 0.0, 120.0, 30.0)
-            .with_title("App")
-            .with_item_vh("FileH", "FileV", &["Open", "Save"])
-            .with_item("Edit", &["Undo"]);
-
-        // 1. Horizontal mode (default)
-        assert!(!menubar.vertical);
-        let labels_h = menubar.text_labels();
-        // Title should be "App", item 0 should be "FileH", item 1 should be "Edit"
-        assert_eq!(labels_h[0].text, "App");
-        assert_eq!(labels_h[1].text, "FileH");
-        assert_eq!(labels_h[2].text, "Edit");
-
-        // Hover test in horizontal layout
-        let ix = menu_item_x("App", &["FileH".to_string(), "Edit".to_string()], 0);
-        let iw = menu_item_w(&["FileH".to_string(), "Edit".to_string()], 0);
-        
-        // Move cursor inside "FileH" bounds
-        menubar.cursor_moved(ix + iw / 2.0, 15.0);
-        assert_eq!(menubar.hovered_menu, Some(0));
-
-        // 2. Vertical mode
-        let mut menubar_v = menubar.with_vertical(true);
-        assert!(menubar_v.vertical);
-        let labels_v = menubar_v.text_labels();
-        // Title should be "App", item 0 should be "FileV", item 1 should be "Edit"
-        assert_eq!(labels_v[0].text, "App");
-        assert_eq!(labels_v[1].text, "FileV");
-        assert_eq!(labels_v[2].text, "Edit");
-
-        // Hover test in vertical layout
-        let iy = menubar_v.item_y_vertical(0);
-        let ih = menubar_v.item_h_vertical();
-
-        // Move cursor inside "FileV" bounds
-        menubar_v.cursor_moved(20.0, iy + ih / 2.0);
-        assert_eq!(menubar_v.hovered_menu, Some(0));
-    }
-
-    #[test]
-    fn test_spreadsheet_dynamic() {
-        let mut spreadsheet = Spreadsheet::new();
-        spreadsheet.set_rect(10.0, 10.0, 100.0, 200.0);
-        spreadsheet.set_visible(true);
-
-        // Initially empty
-        assert!(spreadsheet.headers.is_empty());
-        assert!(spreadsheet.rows.is_empty());
-        assert!(spreadsheet.text_labels().is_empty());
-
-        // Set dynamic headers and rows
-        let headers = vec!["ColA".to_string(), "ColB".to_string()];
-        let rows = vec![
-            vec!["Val1".to_string(), "Val2".to_string()],
-            vec!["Val3".to_string(), "Val4".to_string()],
-        ];
-        spreadsheet.set_spreadsheet_data(headers, rows);
-
-        assert_eq!(spreadsheet.headers.len(), 2);
-        assert_eq!(spreadsheet.rows.len(), 2);
-
-        // Verify labels generated
-        let labels = spreadsheet.text_labels();
-        // 2 headers + 4 cell values = 6 labels total
-        assert_eq!(labels.len(), 6);
-        assert_eq!(labels[0].text, "ColA");
-        assert_eq!(labels[1].text, "ColB");
-        assert_eq!(labels[2].text, "Val1");
-        assert_eq!(labels[3].text, "Val2");
-        assert_eq!(labels[4].text, "Val3");
-        assert_eq!(labels[5].text, "Val4");
-
-        // Verify positions are correct (col 0 starts at x = 10.0 + 8.0 = 18.0)
-        assert_eq!(labels[0].x, 18.0);
-        // Col 1 starts at x = 10.0 + 100.0 * 0.5 + 8.0 = 68.0
-        assert_eq!(labels[1].x, 68.0);
-        assert_eq!(labels[2].x, 18.0);
-        assert_eq!(labels[3].x, 68.0);
-    }
-
-    #[test]
-    fn test_spreadsheet_scrolling() {
-        let mut spreadsheet = Spreadsheet::new();
-        // Visible height is 100px. Header is 24px, so body is 76px.
-        spreadsheet.set_rect(0.0, 0.0, 100.0, 100.0);
-        spreadsheet.set_visible(true);
-
-        let headers = vec!["ColA".to_string()];
-        // Each row is 24px. With 10 rows, content_h = 240px.
-        let mut rows = Vec::new();
-        for i in 0..10 {
-            rows.push(vec![format!("Row{}", i)]);
-        }
-        spreadsheet.set_spreadsheet_data(headers, rows);
-
-        // Content height is 240px, visible height is 100px (body is 76px).
-        // Since content height > visible body height, it should be draggable.
-        assert!(spreadsheet.draggable());
-
-        // Max scroll height = 240.0 - 76.0 = 164.0
-        
-        // Initial scroll position should be 0.0
-        assert_eq!(spreadsheet.scroll_y, 0.0);
-
-        // Scroll down via mouse wheel (positive delta scrolls content down, scroll_y increases via tick)
-        let delta = MouseScrollDelta::LineDelta(0.0, 2.0);
-        // Mouse over spreadsheet (50, 50)
-        let changed = spreadsheet.mouse_wheel(&delta, 50.0, 50.0);
-        assert!(changed);
-        assert_eq!(spreadsheet.scroll_y, 0.0);
-        assert!(spreadsheet.scroll_velocity > 0.0);
-
-        // Tick to apply velocity
-        let mut ticked_change = false;
-        for _ in 0..100 {
-            if spreadsheet.tick(0.016) {
-                ticked_change = true;
-            }
-        }
-        assert!(ticked_change);
-        assert!(spreadsheet.scroll_y > 0.0);
-        assert_eq!(spreadsheet.scroll_velocity, 0.0);
-
-        // Scroll back to top
-        let delta_up = MouseScrollDelta::LineDelta(0.0, -10.0);
-        spreadsheet.mouse_wheel(&delta_up, 50.0, 50.0);
-        assert!(spreadsheet.scroll_velocity < 0.0);
-
-        // Tick back to top
-        for _ in 0..100 {
-            spreadsheet.tick(0.016);
-        }
-        assert_eq!(spreadsheet.scroll_y, 0.0);
-        assert_eq!(spreadsheet.scroll_velocity, 0.0);
-
-        // Drag test
-        // Scrollbar width is 6px. Padding is 2px. Width is 100px.
-        // Scrollbar track x is from 92px to 98px.
-        // Let's drag. Click at (94, 50).
-        spreadsheet.drag_begin(94.0, 50.0);
-        assert!(spreadsheet.is_dragging());
-
-        // Update drag to y = 80
-        let changed_drag = spreadsheet.drag_update(94.0, 80.0);
-        assert!(changed_drag);
-        assert!(spreadsheet.scroll_y > 0.0);
-
-        // End drag
-        spreadsheet.drag_end();
-        assert!(!spreadsheet.is_dragging());
-    }
-
-    #[test]
-    fn test_spreadsheet_zero_height_no_panic() {
-        let mut spreadsheet = Spreadsheet::new();
-        // Visible height is set to 0.0
-        spreadsheet.set_rect(0.0, 0.0, 100.0, 0.0);
-        spreadsheet.set_visible(true);
-
-        let headers = vec!["ColA".to_string()];
-        let mut rows = Vec::new();
-        for i in 0..10 {
-            rows.push(vec![format!("Row{}", i)]);
-        }
-        // This should not panic
-        spreadsheet.set_spreadsheet_data(headers, rows);
-
-        // This should not panic
-        spreadsheet.cursor_moved(50.0, 50.0);
-        
-        let delta = MouseScrollDelta::LineDelta(0.0, -2.0);
-        // This should not panic
-        spreadsheet.mouse_wheel(&delta, 50.0, 50.0);
-
-        // This should not panic
-        assert!(!spreadsheet.draggable());
-
-        // This should not panic
-        spreadsheet.drag_begin(94.0, 50.0);
-        spreadsheet.drag_update(94.0, 80.0);
-        spreadsheet.drag_end();
-
-        // This should not panic and return empty quads for scrollbar
-        let _quads = spreadsheet.extra_quads();
-        // The header quad and divider (if any) are drawn, but scrollbar is not
-        // Let's verify that the scrollbar was not drawn
-        // (the last quad would be the scrollbar thumb with thumb_color if drawn,
-        // but here scrollbar track & thumb shouldn't be added)
-        assert_eq!(spreadsheet.scroll_y, 0.0);
-        
-        // Let's check text labels (should be empty because self.h is 0)
-        let labels = spreadsheet.text_labels();
-        // Headers labels are still generated since they don't depend on scroll/height,
-        // but rows shouldn't be
-        assert_eq!(labels.len(), 1); // Only header ColA
-    }
-
-    #[test]
-    fn test_scroll_box_bounds_scrolling() {
-        let mut sb = ScrollBox::new();
-        sb.set_rect(10.0, 20.0, 100.0, 100.0);
-        
-        // 1. Initially scroll is 0
-        assert_eq!(sb.scroll_y, 0.0);
-
-        // 2. Update bounds: content_h = 150 (greater than viewport_h = 100)
-        sb.update_bounds(150.0, 20.0, 100.0);
-        assert_eq!(sb.scroll_y, 0.0);
-        assert_eq!(sb.content_h, 150.0);
-        assert_eq!(sb.viewport_h, 100.0);
-
-        // 3. Scroll inside bounds
-        let delta = MouseScrollDelta::LineDelta(0.0, -2.0); // scroll down by 2 lines (48px)
-        let changed = sb.mouse_wheel(&delta, 50.0, 50.0);
-        assert!(changed);
-        assert_eq!(sb.scroll_y, 48.0);
-
-        // 4. Clamps at max scroll: 150 - 100 = 50
-        let delta_large = MouseScrollDelta::LineDelta(0.0, -10.0);
-        sb.mouse_wheel(&delta_large, 50.0, 50.0);
-        assert_eq!(sb.scroll_y, 50.0);
-
-        // 5. Test item draw coordinates
-        // Virtual item at virtual_y = 10, item_h = 24
-        // Screen draw y = viewport_y + virtual_y - scroll_y = 20 + 10 - 50 = -20
-        // -20 < viewport_y + 2.0 (22.0), so it should return None (not visible)
-        assert!(sb.get_item_draw_y(10.0, 24.0).is_none());
-
-        // Virtual item at virtual_y = 60, item_h = 24
-        // Screen draw y = 20 + 60 - 50 = 30
-        // 30 >= 22.0 and 30 + 24 <= 118.0, so it should return Some(30.0)
-        assert_eq!(sb.get_item_draw_y(60.0, 24.0), Some(30.0));
-    }
-
-    #[test]
-    fn test_dropdown_widget_interaction() {
-        let options = vec!["Option A".to_string(), "Option B".to_string(), "Option C".to_string()];
-        let mut dd = Dropdown::new(options, 0);
-        dd.set_rect(10.0, 10.0, 100.0, 24.0);
-
-        // 1. Initial State
-        assert!(!dd.open);
-        assert_eq!(dd.selected, 0);
-
-        // 2. Click trigger area opens dropdown
-        let input_changed = dd.mouse_input(MouseButton::Left, ElementState::Pressed, 50.0, 20.0);
-        assert!(input_changed);
-        assert!(dd.open);
-
-        // 3. Hovering options inside popover
-        // Popover starts at y = 10 + 24 = 34. Options are of height 24 each.
-        // Hover option B at y = 34 + 24 + 12 = 70.0
-        let move_changed = dd.cursor_moved(50.0, 70.0);
-        assert!(move_changed);
-        assert_eq!(dd.hovered_item, Some(1));
-
-        // 4. Click option B selects it and closes dropdown
-        let select_changed = dd.mouse_input(MouseButton::Left, ElementState::Pressed, 50.0, 70.0);
-        assert!(select_changed);
-        assert!(!dd.open);
-        assert_eq!(dd.selected, 1);
-        assert!(dd.take_change());
-    }
-
-    #[test]
-    fn test_textbox_selection_highlight() {
-        let mut tb = TextBox::new("Initial Text".to_string());
-        tb.set_rect(10.0, 10.0, 200.0, 30.0);
-
-        // 1. Initial state
-        assert!(!tb.editing);
-        assert!(!tb.all_selected);
-
-        // 2. Click focuses and triggers highlighting
-        let clicked = tb.mouse_input(MouseButton::Left, ElementState::Pressed, 50.0, 20.0);
-        assert!(clicked);
-        assert!(tb.editing);
-        assert!(tb.all_selected);
-        assert_eq!(tb.edit_buffer, "Initial Text");
-
-        // 3. Typing a key replaces all text
-        let key_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Character("A".to_string()),
-            text: Some("A".to_string()),
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        let handled = tb.keyboard_input(&key_ev);
-        assert!(handled);
-        assert!(!tb.all_selected);
-        assert_eq!(tb.edit_buffer, "A");
-
-        // 4. Pressing Enter commits change
-        let enter_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::Enter),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        let handled_enter = tb.keyboard_input(&enter_ev);
-        assert!(handled_enter);
-        assert!(!tb.editing);
-        assert_eq!(tb.text, "A");
-        assert!(tb.take_change());
-    }
-
-    #[test]
-    fn test_textbox_drag_and_modifier_selection() {
-        let mut tb = TextBox::new("Hello World".to_string());
-        tb.set_rect(10.0, 10.0, 200.0, 30.0);
-
-        // 1. Initial click focuses and selects all
-        let pressed = tb.mouse_input(MouseButton::Left, ElementState::Pressed, 50.0, 20.0);
-        assert!(pressed);
-        let released = tb.mouse_input(MouseButton::Left, ElementState::Released, 50.0, 20.0);
-        assert!(released);
-        assert!(tb.editing);
-        assert!(tb.all_selected);
-        assert_eq!(tb.cursor_idx, 11);
-        assert_eq!(tb.select_anchor, Some(0));
-
-        // 2. Click inside placed caret at index 5 (x = 10 + 8 + 5 * 7.2 = 54)
-        let pressed_inside = tb.mouse_input(MouseButton::Left, ElementState::Pressed, 54.0, 20.0);
-        assert!(pressed_inside);
-        assert_eq!(tb.cursor_idx, 5);
-        assert_eq!(tb.select_anchor, Some(5));
-        assert!(!tb.all_selected);
-
-        // 3. Drag to index 11 (x = 10 + 8 + 11 * 7.2 = 97.2)
-        tb.drag_begin(54.0, 20.0);
-        let updated = tb.drag_update(97.2, 20.0);
-        assert!(updated);
-        assert_eq!(tb.cursor_idx, 11);
-        assert_eq!(tb.select_anchor, Some(5));
-        tb.drag_end();
-
-        // 4. Keyboard ArrowLeft with Shift shrinks selection from 11 to 10
-        let left_shift_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::ArrowLeft),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: true,
-        };
-        let handled = tb.keyboard_input(&left_shift_ev);
-        assert!(handled);
-        assert_eq!(tb.cursor_idx, 10);
-        assert_eq!(tb.select_anchor, Some(5));
-
-        // 5. Keyboard ArrowLeft without Shift collapses selection to start (index 5)
-        let left_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::ArrowLeft),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        let handled = tb.keyboard_input(&left_ev);
-        assert!(handled);
-        assert_eq!(tb.cursor_idx, 5);
-        assert_eq!(tb.select_anchor, None);
-
-        // 6. Keyboard Shift+Up highlights to beginning (cursor 0, anchor 5)
-        let up_shift_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::ArrowUp),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: true,
-        };
-        let handled = tb.keyboard_input(&up_shift_ev);
-        assert!(handled);
-        assert_eq!(tb.cursor_idx, 0);
-        assert_eq!(tb.select_anchor, Some(5));
-
-        // 7. Typing a key replaces selected range "Hello" with "Rust"
-        let rust_ev = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Character("Rust".to_string()),
-            text: Some("Rust".to_string()),
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        let handled = tb.keyboard_input(&rust_ev);
-        assert!(handled);
-        assert_eq!(tb.edit_buffer, "Rust World");
-        assert_eq!(tb.cursor_idx, 4);
-        assert_eq!(tb.select_anchor, None);
-    }
-
-    #[test]
-    fn test_paginator_rotated_tabs() {
-        let pages = vec!["📁 Browse".to_string(), "🌐 Network".to_string()];
-        let mut paginator = Paginator::new(56.0, pages)
-            .with_tab_y_offset(100.0)
-            .with_tabs_rotated(true);
-        
-        paginator.set_rect(0.0, 0.0, 1000.0, 600.0);
-
-        // Verify target_y is updated correctly
-        // selected_page = 0: tab_y_offset = 100.0
-        assert_eq!(paginator.target_y, 100.0);
-
-        paginator.set_selected_page(1);
-        // selected_page = 1: tab_y_offset + 1 * (120.0 + 10.0) = 230.0
-        assert_eq!(paginator.target_y, 230.0);
-
-        // Verify hover coordinates
-        // Tab 0 bx/by bounds:
-        // tab_w = 32.0, tab_h = 120.0, spacing = 10.0, sidebar_w = 48.0
-        // bx = self.x + (sidebar_w - tab_w) / 2 = 8.0
-        // by = self.y + tab_y_offset + i * 130.0 = 100.0
-        // bx range: [8.0, 40.0], by range: [100.0, 220.0]
-        
-        // Hover at (24.0, 150.0) should hit Tab 0
-        let hover_tab0 = paginator.on_cursor_moved(24.0, 150.0);
-        assert!(hover_tab0);
-        assert_eq!(paginator.hovered_tab, Some(0));
-
-        // Hover at (24.0, 280.0) should hit Tab 1 (by range: [230.0, 350.0])
-        let hover_tab1 = paginator.on_cursor_moved(24.0, 280.0);
-        assert!(hover_tab1);
-        assert_eq!(paginator.hovered_tab, Some(1));
-
-        // Clicking Tab 0 selects it
-        let click_tab0 = paginator.mouse_input(MouseButton::Left, ElementState::Pressed, 24.0, 150.0);
-        assert!(click_tab0);
-        assert_eq!(paginator.pressed_tab, Some(0));
-
-        let release_tab0 = paginator.mouse_input(MouseButton::Left, ElementState::Released, 24.0, 150.0);
-        assert!(release_tab0);
-        assert_eq!(paginator.selected_page, 0);
-
-        // Check vertical text formatting
-        let labels = paginator.text_labels();
-        assert_eq!(labels.len(), 2);
-        assert_eq!(labels[0].text, "📁");
-        assert_eq!(labels[1].text, "🌐");
-        
-        // Check that rotated text quads were generated
-        assert!(!paginator.tab_text_quads[0].is_empty());
-        assert!(!paginator.tab_text_quads[1].is_empty());
-    }
-
-    #[test]
-    fn test_svg_text_rendering() {
-        let svg_data = r##"<svg width="32" height="120" xmlns="http://www.w3.org/2000/svg">
-  <text x="16" y="60" font-family="sans-serif" font-size="12" fill="#E6E6F2" text-anchor="middle" dominant-baseline="middle" transform="rotate(-90 16 60)">Audio</text>
-</svg>"##.as_bytes();
-
-        let opt = resvg::usvg::Options::default();
-        let mut fontdb = resvg::usvg::fontdb::Database::new();
-        fontdb.load_system_fonts();
-        let tree = resvg::usvg::Tree::from_data(svg_data, &opt, &fontdb).unwrap();
-        
-        let mut pixmap = resvg::tiny_skia::Pixmap::new(32, 120).unwrap();
-        resvg::render(&tree, resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut());
-        
-        pixmap.save_png("/home/lsgalante/Dropbox/Clear/scratch/test_svg.png").unwrap();
-
-        // Check that some pixels were drawn (are non-transparent)
-        let pixels = pixmap.data();
-        let mut non_transparent = 0;
-        for i in (3..pixels.len()).step_by(4) {
-            if pixels[i] > 0 {
-                non_transparent += 1;
-            }
-        }
-        assert!(non_transparent > 0, "Should have rendered some text pixels");
-    }
-}
-
-// Generic text item layout wrapper
-#[derive(Debug)]
-pub struct TextItem {
-    pub buffer: glyphon::Buffer,
-    pub x: f32,
-    pub y: f32,
-    pub color: glyphon::Color,
-    pub bounds: Option<[f32; 4]>,
-}
-
-// Styled label builder with optional strikethrough
-#[derive(Debug)]
-pub struct StyledLabel {
-    pub buffer: glyphon::Buffer,
-    pub w: f32,
-    pub color: [f32; 4],
-    pub g_color: glyphon::Color,
-    pub strikethrough: bool,
-    pub strikethrough_color: Option<[f32; 4]>,
-}
-
-impl StyledLabel {
-    pub fn new(fs: &mut glyphon::FontSystem, text: &str, size: f32, color: [f32; 4]) -> Self {
-        Self::new_with_family(fs, text, size, color, "sans-serif")
-    }
-
-    pub fn new_with_family(fs: &mut glyphon::FontSystem, text: &str, size: f32, color: [f32; 4], family: &str) -> Self {
-        let metrics = glyphon::Metrics::new(size, size * 1.4);
-        let mut buffer = glyphon::Buffer::new(fs, metrics);
-        let attrs = glyphon::Attrs::new().family(glyphon::Family::Name(family));
-        buffer.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
-        buffer.shape_until_scroll(fs, true);
-        let w = buffer.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
-        let g_color = glyphon::Color::rgb(
-            (color[0] * 255.0) as u8,
-            (color[1] * 255.0) as u8,
-            (color[2] * 255.0) as u8,
-        );
-        Self {
-            buffer,
-            w,
-            color,
-            g_color,
-            strikethrough: false,
-            strikethrough_color: None,
-        }
-    }
-
-    pub fn with_strikethrough(mut self, enabled: bool) -> Self {
-        self.strikethrough = enabled;
-        self
-    }
-
-    pub fn with_strikethrough_color(mut self, color: [f32; 4]) -> Self {
-        self.strikethrough_color = Some(color);
-        self
-    }
-
-    pub fn draw(self, text_items: &mut Vec<TextItem>, x: f32, y: f32) -> f32 {
-        let w = self.w;
-        text_items.push(TextItem {
-            buffer: self.buffer,
-            x,
-            y,
-            color: self.g_color,
-            bounds: None,
-        });
-        w
-    }
-
-    pub fn strikethrough_rect(&self, x: f32, y: f32, scale: f32) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        if self.strikethrough {
-            let col = self.strikethrough_color.unwrap_or(self.color);
-            let font_size = self.buffer.metrics().font_size;
-            let line_y = self.buffer.layout_runs().next().map(|r| r.line_y).unwrap_or(font_size * 1.05);
-            let offset_y = line_y - 0.28 * font_size;
-            Some((
-                x,
-                y + offset_y * scale,
-                self.w,
-                1.0 * scale,
-                col,
-            ))
-        } else {
-            None
-        }
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct ScrollingList {
-    pub scroll_box: ScrollBox,
-    pub item_height: f32,
-    pub item_gap: f32,
-}
-
-impl ScrollingList {
-    pub fn new(item_height: f32, item_gap: f32) -> Self {
-        Self {
-            scroll_box: ScrollBox::new(),
-            item_height,
-            item_gap,
-        }
-    }
-
-    pub fn update_bounds(&mut self, count: usize, viewport_y: f32, viewport_h: f32) {
-        let item_height_full = self.item_height + self.item_gap;
-        let content_h = count as f32 * item_height_full;
-        self.scroll_box.update_bounds(content_h, viewport_y, viewport_h);
-    }
-
-    pub fn get_item_draw_y(&self, idx: usize, offset: f32) -> Option<f32> {
-        let item_height_full = self.item_height + self.item_gap;
-        let virtual_y = idx as f32 * item_height_full + offset;
-        self.scroll_box.get_item_draw_y(virtual_y, self.item_height)
-    }
-
-    pub fn scroll_y(&self) -> f32 {
-        self.scroll_box.scroll_y
-    }
-
-    pub fn set_scroll_y(&mut self, val: f32) {
-        self.scroll_box.scroll_y = val;
-    }
-}
-
-impl Default for ScrollingList {
-    fn default() -> Self {
-        Self::new(24.0, 4.0)
-    }
-}
-
-impl Widget for ScrollingList {
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        self.scroll_box.rect()
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.scroll_box.set_rect(x, y, w, h);
-    }
-
-    fn color(&self) -> [f32; 4] {
-        self.scroll_box.color()
-    }
-
-    fn set_hovered(&mut self, v: bool) {
-        self.scroll_box.set_hovered(v);
-    }
-
-    fn hovered(&self) -> bool {
-        self.scroll_box.hovered()
-    }
-
-    fn highlight_color(&self) -> Option<[f32; 4]> {
-        self.scroll_box.highlight_color()
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        self.scroll_box.cursor_moved(px, py)
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        self.scroll_box.mouse_wheel(delta, px, py)
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        self.scroll_box.mouse_input(button, state, px, py)
-    }
-
-    fn focus(&mut self) {
-        self.scroll_box.focus();
-    }
-
-    fn unfocus(&mut self) {
-        self.scroll_box.unfocus();
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        self.scroll_box.extra_quads()
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        self.scroll_box.keyboard_input(event)
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.scroll_box.parent() }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.scroll_box.set_parent(parent); }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.scroll_box.children() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.scroll_box.add_child(child); }
-    fn clear_children(&mut self) { self.scroll_box.clear_children(); }
-
-    fn update_bounds(&mut self, count: usize, viewport_y: f32, viewport_h: f32) {
-        self.update_bounds(count, viewport_y, viewport_h);
-    }
-
-    fn get_item_draw_y(&self, idx: usize, offset: f32) -> Option<f32> {
-        self.get_item_draw_y(idx, offset)
-    }
-}
-
-unsafe impl Send for Container {}
-unsafe impl Sync for Container {}
-unsafe impl Send for ScrollBox {}
-unsafe impl Sync for ScrollBox {}
-unsafe impl Send for Spinbox {}
-unsafe impl Sync for Spinbox {}
-unsafe impl Send for ColorSelector {}
-unsafe impl Sync for ColorSelector {}
-unsafe impl Send for ScrollingList {}
-unsafe impl Sync for ScrollingList {}
-
-// ── Dropdown Widget ──
-
-#[derive(Debug, Clone)]
-pub struct Dropdown {
-    base: WidgetBase,
-    pub options: Vec<String>,
-    pub selected: usize,
-    pub open: bool,
-    hovered_item: Option<usize>,
-    just_changed: bool,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-}
-
-impl Dropdown {
-    pub fn new(options: Vec<String>, selected: usize) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            options,
-            selected,
-            open: false,
-            hovered_item: None,
-            just_changed: false,
-            parent: None,
-            children: Vec::new(),
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.base.label = Some(label.to_string());
-    }
-
-    pub fn take_change(&mut self) -> bool {
-        let changed = self.just_changed;
-        self.just_changed = false;
-        changed
-    }
-
-    pub fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        if !self.open { return; }
-        
-        let dy = self.base.y + self.base.h;
-        let dh = self.options.len() as f32 * 24.0;
-        
-        // 1. Soft layered drop shadows
-        pc.rect([0.02, 0.02, 0.05, 0.15], self.base.x + 1.0, dy + 1.0, self.base.w, dh);
-        pc.rect([0.02, 0.02, 0.05, 0.08], self.base.x + 3.0, dy + 3.0, self.base.w, dh);
-        pc.rect([0.02, 0.02, 0.05, 0.04], self.base.x + 5.0, dy + 5.0, self.base.w, dh);
-
-        // 2. High-contrast premium outer border
-        pc.rect([0.25, 0.35, 0.50, 0.40], self.base.x, dy, self.base.w, dh);
-        
-        // 3. Frosted glass background (matching magic alpha 0.699 in shader)
-        pc.rect([0.06, 0.06, 0.09, 0.699], self.base.x + 1.0, dy + 1.0, self.base.w - 2.0, dh - 2.0); // bg
-        
-        if let Some(h_idx) = self.hovered_item {
-            let iy = dy + h_idx as f32 * 24.0;
-            // 4. Vibrantly colored translucent selection highlight
-            pc.rect([0.20, 0.45, 0.85, 0.50], self.base.x + 2.0, iy + 2.0, self.base.w - 4.0, 20.0);
-        }
-        
-        for (idx, opt) in self.options.iter().enumerate() {
-            let iy = dy + idx as f32 * 24.0 + (24.0 - 12.0) / 2.0;
-            let text_color = if self.hovered_item == Some(idx) {
-                [0xff, 0xff, 0xff]
-            } else if self.selected == idx {
-                [0x3a, 0x9a, 0xff]
-            } else {
-                [0xcc, 0xcc, 0xd4]
-            };
-            
-            pc.text(
-                opt,
-                self.base.x + 8.0,
-                iy,
-                12.0,
-                [
-                    text_color[0] as f32 / 255.0,
-                    text_color[1] as f32 / 255.0,
-                    text_color[2] as f32 / 255.0,
-                    1.0,
-                ],
-            );
-        }
-    }
-}
-
-impl Default for Dropdown {
-    fn default() -> Self {
-        Self::new(Vec::new(), 0)
-    }
-}
-
-impl Widget for Dropdown {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn color(&self) -> [f32; 4] {
-        [0.08, 0.08, 0.12, 1.0]
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        let (x, y, w, h) = self.rect();
-        let hx = if self.base.row_w > 0.0 { self.base.row_x } else { x };
-        let hw = if self.base.row_w > 0.0 { self.base.row_w } else { w };
-        let top = self.top_room();
-        let hy = y - top;
-        let hh = h + top;
-        if self.open {
-            let dy = y + h;
-            let dh = self.options.len() as f32 * 24.0;
-            let hit_trigger = px >= hx && px <= hx + hw && py >= hy && py <= hy + hh;
-            let hit_popover = px >= x && px <= x + w && py >= dy && py <= dy + dh;
-            hit_trigger || hit_popover
-        } else {
-            px >= hx && px <= hx + hw && py >= hy && py <= hy + hh
-        }
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was_hovered = self.base.hovered;
-        let was_hovered_item = self.hovered_item;
-        
-        self.base.hovered = self.hit_test(px, py);
-        self.hovered_item = None;
-
-        if self.open {
-            let (x, y, w, h) = self.rect();
-            let dy = y + h;
-            let dh = self.options.len() as f32 * 24.0;
-            if px >= x && px <= x + w && py >= dy && py <= dy + dh {
-                let idx = ((py - dy) / 24.0) as usize;
-                if idx < self.options.len() {
-                    self.hovered_item = Some(idx);
-                }
-            }
-        }
-
-        self.base.hovered != was_hovered || self.hovered_item != was_hovered_item
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left || state != ElementState::Pressed { return false; }
-
-        let (x, y, w, h) = self.rect();
-        let (hy, hh) = if self.base.label.is_some() {
-            (y - 18.0, h + 18.0)
-        } else {
-            (y, h)
-        };
-        let dy = y + h;
-        let dh = self.options.len() as f32 * 24.0;
-
-        let inside_trigger = px >= x && px <= x + w && py >= hy && py <= hy + hh;
-        let inside_popover = self.open && px >= x && px <= x + w && py >= dy && py <= dy + dh;
-
-        if inside_popover {
-            let idx = ((py - dy) / 24.0) as usize;
-            if idx < self.options.len() {
-                if self.selected != idx {
-                    self.selected = idx;
-                    self.just_changed = true;
-                }
-            }
-            self.open = false;
-            return true;
-        }
-
-        if inside_trigger {
-            self.open = !self.open;
-            if self.open {
-                self.focus();
-            } else {
-                self.unfocus();
-            }
-            return true;
-        }
-
-        if self.open {
-            self.open = false;
-            return true;
-        }
-
-        false
-    }
-
-    fn focus(&mut self) {
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        self.open = false;
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if event.state != ElementState::Pressed { return false; }
-        if !self.open {
-            if let Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) = event.logical_key {
-                self.open = true;
-                self.hovered_item = Some(self.selected);
-                return true;
-            }
-            return false;
-        }
-        
-        match event.logical_key {
-            Key::Named(NamedKey::ArrowDown) => {
-                let current = self.hovered_item.unwrap_or(self.selected);
-                if current + 1 < self.options.len() {
-                    self.hovered_item = Some(current + 1);
-                } else {
-                    self.hovered_item = Some(0);
-                }
-                true
-            }
-            Key::Named(NamedKey::ArrowUp) => {
-                let current = self.hovered_item.unwrap_or(self.selected);
-                if current > 0 {
-                    self.hovered_item = Some(current - 1);
-                } else {
-                    self.hovered_item = Some(self.options.len() - 1);
-                }
-                true
-            }
-            Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Space) => {
-                if let Some(idx) = self.hovered_item {
-                    if self.selected != idx {
-                        self.selected = idx;
-                        self.just_changed = true;
-                    }
-                }
-                self.open = false;
-                true
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.open = false;
-                true
-            }
-            _ => false
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-
-        let bg_color = [0.08, 0.08, 0.12, 1.0];
-        let border_color = if self.open {
-            [0.30, 0.50, 0.32, 1.0]
-        } else if self.base.hovered {
-            [0.25, 0.25, 0.35, 1.0]
-        } else {
-            [0.18, 0.18, 0.24, 1.0]
-        };
-
-        quads.push((self.base.x, self.base.y, self.base.w, self.base.h, border_color));
-        quads.push((self.base.x + 1.0, self.base.y + 1.0, self.base.w - 2.0, self.base.h - 2.0, bg_color));
-
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x + 4.0,
-                y: self.base.y - 14.0,
-                font_size: 11.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-
-        let selected_text = self.options.get(self.selected).cloned().unwrap_or_default();
-        labels.push(TextLabel {
-            text: selected_text,
-            x: self.base.x + 8.0,
-            y: self.base.y + (self.base.h - 12.0) / 2.0,
-            font_size: 12.0,
-            color: [0xdd, 0xdd, 0xe2],
-        });
-
-        labels.push(TextLabel {
-            text: "▼".to_string(),
-            x: self.base.x + self.base.w - 18.0,
-            y: self.base.y + (self.base.h - 10.0) / 2.0,
-            font_size: 10.0,
-            color: [0x83, 0x83, 0x8a],
-        });
-
-        labels
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-    fn value(&self) -> i32 { self.selected as i32 }
-    fn take_click(&mut self) -> bool { self.take_change() }
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        if self.open {
-            Some((self.base.x, self.base.y + self.base.h, self.base.w, self.options.len() as f32 * 24.0))
-        } else {
-            None
-        }
-    }
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        Dropdown::render_popover(self, pc);
-    }
-}
-
-unsafe impl Send for Dropdown {}
-unsafe impl Sync for Dropdown {}
-
-// ── TextBox Widget ──
-
-#[derive(Debug, Clone)]
-pub struct TextBox {
-    base: WidgetBase,
-    pub text: String,
-    pub editing: bool,
-    pub edit_buffer: String,
-    just_changed: bool,
-    pub disabled: bool,
-    pub all_selected: bool,
-    pub cursor_idx: usize,
-    pub select_anchor: Option<usize>,
-    pub dragging: bool,
-    pub just_focused: bool,
-    pub drag_start_idx: Option<usize>,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-    pub max_width: Option<f32>,
-    pub width: Option<f32>,
-    pub is_password: bool,
-    pub multiline: bool,
-    pub draw_bg_border: bool,
-    pub text_color: Option<[u8; 3]>,
-    pub font_size: f32,
-    pub font_family: String,
-}
-
-impl TextBox {
-    pub fn new(text: String) -> Self {
-        Self {
-            base: WidgetBase::new(),
-            text,
-            editing: false,
-            edit_buffer: String::new(),
-            just_changed: false,
-            disabled: false,
-            all_selected: false,
-            cursor_idx: 0,
-            select_anchor: None,
-            dragging: false,
-            just_focused: false,
-            drag_start_idx: None,
-            parent: None,
-            children: Vec::new(),
-            max_width: Some(300.0),
-            width: None,
-            is_password: false,
-            multiline: false,
-            draw_bg_border: true,
-            text_color: None,
-            font_size: 12.0,
-            font_family: "monospace".to_string(),
-        }
-    }
-
-    pub fn with_multiline(mut self, multiline: bool) -> Self {
-        self.multiline = multiline;
-        self
-    }
-
-    pub fn with_draw_bg_border(mut self, draw: bool) -> Self {
-        self.draw_bg_border = draw;
-        self
-    }
-
-    pub fn with_text_color(mut self, color: Option<[u8; 3]>) -> Self {
-        self.text_color = color;
-        self
-    }
-
-    pub fn with_font_size(mut self, size: f32) -> Self {
-        self.font_size = size;
-        self
-    }
-
-    pub fn with_font_family(mut self, family: String) -> Self {
-        self.font_family = family;
-        self
-    }
-
-    pub fn wrap_text(&self, max_chars_per_line: usize) -> (Vec<String>, Vec<(usize, usize)>) {
-        let text_src = if self.editing { &self.edit_buffer } else { &self.text };
-        let chars: Vec<char> = text_src.chars().collect();
-        let mut lines = Vec::new();
-        let mut current_line = Vec::new();
-        let mut index_map = vec![(0, 0); chars.len() + 1];
-        
-        let max_chars = max_chars_per_line.max(1);
-        
-        let mut i = 0;
-        while i < chars.len() {
-            let ch = chars[i];
-            
-            if ch == '\n' {
-                index_map[i] = (lines.len(), current_line.len());
-                lines.push(current_line.iter().collect::<String>());
-                current_line.clear();
-                i += 1;
-                continue;
-            }
-            
-            current_line.push(ch);
-            index_map[i] = (lines.len(), current_line.len() - 1);
-            
-            if current_line.len() > max_chars {
-                let mut space_idx = None;
-                for (s_idx, &c) in current_line.iter().enumerate().rev() {
-                    if c.is_whitespace() {
-                        space_idx = Some(s_idx);
-                        break;
-                    }
-                }
-                
-                if let Some(s_idx) = space_idx {
-                    let line_to_push: Vec<char> = current_line[0..s_idx + 1].to_vec();
-                    let remaining: Vec<char> = current_line[s_idx + 1..].to_vec();
-                    
-                    let line_idx = lines.len();
-                    lines.push(line_to_push.iter().collect::<String>());
-                    
-                    current_line = remaining;
-                    let start_orig = i - current_line.len() + 1;
-                    for c_idx in 0..current_line.len() {
-                        index_map[start_orig + c_idx] = (line_idx + 1, c_idx);
-                    }
-                } else {
-                    let line_to_push: Vec<char> = current_line[0..max_chars].to_vec();
-                    let remaining: Vec<char> = current_line[max_chars..].to_vec();
-                    
-                    let line_idx = lines.len();
-                    lines.push(line_to_push.iter().collect::<String>());
-                    
-                    current_line = remaining;
-                    let start_orig = i - current_line.len() + 1;
-                    for c_idx in 0..current_line.len() {
-                        index_map[start_orig + c_idx] = (line_idx + 1, c_idx);
-                    }
-                }
-            }
-            i += 1;
-        }
-        
-        index_map[chars.len()] = (lines.len(), current_line.len());
-        lines.push(current_line.iter().collect::<String>());
-        
-        (lines, index_map)
-    }
-
-    pub fn map_2d_to_1d(&self, index_map: &[(usize, usize)], target_line: usize, target_col: usize, max_line_idx: usize) -> usize {
-        let line = target_line.min(max_line_idx);
-        let mut best_idx = 0;
-        let mut best_dist = usize::MAX;
-        
-        for (i, &(l, c)) in index_map.iter().enumerate() {
-            if l == line {
-                let dist = (c as isize - target_col as isize).abs() as usize;
-                if dist < best_dist {
-                    best_dist = dist;
-                    best_idx = i;
-                }
-            }
-        }
-        best_idx
-    }
-
-    pub fn with_password(mut self, is_password: bool) -> Self {
-        self.is_password = is_password;
-        self
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.base.label = Some(label.to_string());
-    }
-
-    pub fn take_change(&mut self) -> bool {
-        let changed = self.just_changed;
-        self.just_changed = false;
-        changed
-    }
-
-    pub fn with_max_width(mut self, max_w: Option<f32>) -> Self {
-        self.max_width = max_w;
-        self
-    }
-
-    pub fn set_max_width(&mut self, max_w: Option<f32>) {
-        self.max_width = max_w;
-    }
-
-    pub fn with_width(mut self, w: f32) -> Self {
-        self.width = Some(w);
-        self
-    }
-
-    pub fn set_width(&mut self, w: f32) {
-        self.width = Some(w);
-    }
-
-    pub fn copy_selection(&self) {
-        let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-        let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-        if start != end {
-            let chars: Vec<char> = self.edit_buffer.chars().collect();
-            let selected_text: String = chars[start..end].iter().collect();
-            clipboard::copy_to_clipboard(&selected_text);
-        }
-    }
-
-    pub fn cut_selection(&mut self) -> bool {
-        let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-        let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-        if start != end {
-            let chars: Vec<char> = self.edit_buffer.chars().collect();
-            let selected_text: String = chars[start..end].iter().collect();
-            clipboard::copy_to_clipboard(&selected_text);
-
-            let mut new_buf = String::new();
-            for i in 0..start {
-                new_buf.push(chars[i]);
-            }
-            for i in end..chars.len() {
-                new_buf.push(chars[i]);
-            }
-            self.edit_buffer = new_buf;
-            self.cursor_idx = start;
-            self.select_anchor = None;
-            self.all_selected = false;
-            return true;
-        }
-        false
-    }
-
-    pub fn paste_from_clipboard(&mut self) -> bool {
-        if let Some(text) = clipboard::read_from_clipboard() {
-            let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-            let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-            let chars: Vec<char> = self.edit_buffer.chars().collect();
-            let mut new_buf = String::new();
-            for i in 0..start {
-                new_buf.push(chars[i]);
-            }
-            let mut inserted_count = 0;
-            for ch in text.chars() {
-                if !ch.is_control() && ch != '\n' && ch != '\r' {
-                    new_buf.push(ch);
-                    inserted_count += 1;
-                }
-            }
-            for i in end..chars.len() {
-                new_buf.push(chars[i]);
-            }
-            self.edit_buffer = new_buf;
-            self.cursor_idx = start + inserted_count;
-            self.select_anchor = None;
-            self.all_selected = false;
-            true
-        } else {
-            false
-        }
-    }
-
-    pub fn select_all(&mut self) {
-        let len = self.edit_buffer.chars().count();
-        self.select_anchor = Some(0);
-        self.cursor_idx = len;
-        self.all_selected = len > 0;
-    }
-}
-
-impl Default for TextBox {
-    fn default() -> Self {
-        Self::new(String::new())
-    }
-}
-
-impl Widget for TextBox {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.base.x, self.base.y, self.base.w, self.base.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        let final_w = if let Some(explicit_w) = self.width {
-            explicit_w
-        } else if let Some(max_w) = self.max_width {
-            w.min(max_w)
-        } else {
-            w
-        };
-        if let Some(b) = self.base_mut() {
-            b.x = x;
-            b.y = y;
-            b.w = final_w;
-            b.h = h;
-        }
-    }
-    fn set_row_rect(&mut self, x: f32, w: f32) {
-        let final_w = if let Some(explicit_w) = self.width {
-            explicit_w
-        } else if let Some(max_w) = self.max_width {
-            w.min(max_w)
-        } else {
-            w
-        };
-        if let Some(b) = self.base_mut() {
-            b.row_x = x;
-            b.row_w = final_w;
-        }
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.10, 0.10, 0.16, 1.0]
-    }
-
-
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        if self.disabled {
-            let was = self.base.hovered;
-            self.base.hovered = false;
-            return was;
-        }
-        let mut changed = false;
-        if self.dragging && self.editing {
-            let char_width = self.font_size * 0.6;
-            let drag_idx = if self.multiline {
-                let line_height = self.font_size * 1.333;
-                let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-                let (lines, index_map) = self.wrap_text(max_chars);
-                let click_line = (((py - (self.base.y + 8.0)) / line_height).floor() as isize).max(0) as usize;
-                let click_col = (((px - (self.base.x + 8.0)) / char_width).round() as isize).max(0) as usize;
-                self.map_2d_to_1d(&index_map, click_line, click_col, lines.len() - 1)
-            } else {
-                (((px - (self.base.x + 8.0)) / char_width).round() as isize)
-                    .max(0)
-                    .min(self.edit_buffer.chars().count() as isize) as usize
-            };
-            if self.cursor_idx != drag_idx {
-                self.cursor_idx = drag_idx;
-                self.just_focused = false;
-                let len = self.edit_buffer.chars().count();
-                let start = self.select_anchor.unwrap_or(0).min(self.cursor_idx);
-                let end = self.select_anchor.unwrap_or(0).max(self.cursor_idx);
-                self.all_selected = start == 0 && end == len && len > 0;
-                changed = true;
-            }
-        }
-        let was = self.base.hovered;
-        self.base.hovered = self.hit_test(px, py);
-        if was != self.base.hovered {
-            changed = true;
-        }
-        changed
-    }
-
-    fn draggable(&self) -> bool { !self.disabled }
-    fn is_dragging(&self) -> bool { self.dragging }
-    fn widget_font(&self) -> Option<String> { Some(self.font_family.clone()) }
-
-    fn drag_begin(&mut self, _px: f32, _py: f32) {
-        if self.disabled || !self.editing { return; }
-        self.dragging = true;
-    }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        if self.disabled || !self.editing { return false; }
-        let char_width = self.font_size * 0.6;
-        let drag_idx = if self.multiline {
-            let line_height = self.font_size * 1.333;
-            let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-            let (lines, index_map) = self.wrap_text(max_chars);
-            let click_line = (((py - (self.base.y + 8.0)) / line_height).floor() as isize).max(0) as usize;
-            let click_col = (((px - (self.base.x + 8.0)) / char_width).round() as isize).max(0) as usize;
-            self.map_2d_to_1d(&index_map, click_line, click_col, lines.len() - 1)
-        } else {
-            (((px - (self.base.x + 8.0)) / char_width).round() as isize)
-                .max(0)
-                .min(self.edit_buffer.chars().count() as isize) as usize
-        };
-        if self.cursor_idx != drag_idx {
-            self.cursor_idx = drag_idx;
-            self.just_focused = false;
-            let len = self.edit_buffer.chars().count();
-            let start = self.select_anchor.unwrap_or(0).min(self.cursor_idx);
-            let end = self.select_anchor.unwrap_or(0).max(self.cursor_idx);
-            self.all_selected = start == 0 && end == len && len > 0;
-            return true;
-        }
-        false
-    }
-
-    fn drag_end(&mut self) {
-        self.dragging = false;
-    }
-
-    fn value(&self) -> i32 { 0 }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if self.disabled { return false; }
-        if button != MouseButton::Left { return false; }
-        if !self.hit_test(px, py) { return false; }
-        match state {
-            ElementState::Pressed => {
-                if !self.editing {
-                    self.focus();
-                } else {
-                    let char_width = self.font_size * 0.6;
-                    let idx = if self.multiline {
-                        let line_height = self.font_size * 1.333;
-                        let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-                        let (lines, index_map) = self.wrap_text(max_chars);
-                        let click_line = (((py - (self.base.y + 8.0)) / line_height).floor() as isize).max(0) as usize;
-                        let click_col = (((px - (self.base.x + 8.0)) / char_width).round() as isize).max(0) as usize;
-                        self.map_2d_to_1d(&index_map, click_line, click_col, lines.len() - 1)
-                    } else {
-                        (((px - (self.base.x + 8.0)) / char_width).round() as isize)
-                            .max(0)
-                            .min(self.edit_buffer.chars().count() as isize) as usize
-                    };
-                    self.cursor_idx = idx;
-                    self.select_anchor = Some(idx);
-                    self.all_selected = false;
-                }
-                true
-            }
-            ElementState::Released => {
-                if self.dragging {
-                    self.drag_end();
-                }
-                if self.select_anchor == Some(self.cursor_idx) {
-                    self.select_anchor = None;
-                }
-                true
-            }
-        }
-    }
-
-    fn focus(&mut self) {
-        if self.disabled { return; }
-        self.editing = true;
-        self.edit_buffer = self.text.clone();
-        let len = self.edit_buffer.chars().count();
-        self.cursor_idx = len;
-        self.select_anchor = Some(0);
-        self.all_selected = len > 0;
-        self.just_focused = true;
-        focus::set_focused(self);
-    }
-
-    fn unfocus(&mut self) {
-        if self.editing {
-            self.editing = false;
-            if self.text != self.edit_buffer {
-                self.text = self.edit_buffer.clone();
-                self.just_changed = true;
-            }
-            self.select_anchor = None;
-            self.all_selected = false;
-        }
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !self.editing || self.disabled { return false; }
-        if event.state != ElementState::Pressed { return false; }
-        
-        let control = event.ctrl;
-        
-        match &event.logical_key {
-            Key::Named(NamedKey::Backspace) => {
-                if self.all_selected {
-                    self.edit_buffer.clear();
-                    self.cursor_idx = 0;
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                    return true;
-                }
-                let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-                let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-                if start != end {
-                    let chars: Vec<char> = self.edit_buffer.chars().collect();
-                    let mut new_buf = String::new();
-                    for i in 0..start {
-                        new_buf.push(chars[i]);
-                    }
-                    for i in end..chars.len() {
-                        new_buf.push(chars[i]);
-                    }
-                    self.edit_buffer = new_buf;
-                    self.cursor_idx = start;
-                    self.select_anchor = None;
-                    return true;
-                }
-                if self.cursor_idx > 0 {
-                    let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                    chars.remove(self.cursor_idx - 1);
-                    self.edit_buffer = chars.into_iter().collect();
-                    self.cursor_idx -= 1;
-                    self.select_anchor = None;
-                    return true;
-                }
-                false
-            }
-            Key::Named(NamedKey::Delete) => {
-                if self.all_selected {
-                    self.edit_buffer.clear();
-                    self.cursor_idx = 0;
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                    return true;
-                }
-                let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-                let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-                if start != end {
-                    let chars: Vec<char> = self.edit_buffer.chars().collect();
-                    let mut new_buf = String::new();
-                    for i in 0..start {
-                        new_buf.push(chars[i]);
-                    }
-                    for i in end..chars.len() {
-                        new_buf.push(chars[i]);
-                    }
-                    self.edit_buffer = new_buf;
-                    self.cursor_idx = start;
-                    self.select_anchor = None;
-                    return true;
-                }
-                if self.cursor_idx < self.edit_buffer.chars().count() {
-                    let mut chars: Vec<char> = self.edit_buffer.chars().collect();
-                    chars.remove(self.cursor_idx);
-                    self.edit_buffer = chars.into_iter().collect();
-                    self.select_anchor = None;
-                    return true;
-                }
-                false
-            }
-            Key::Named(NamedKey::ArrowLeft) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                    if self.cursor_idx > 0 {
-                        self.cursor_idx -= 1;
-                        true
-                    } else {
-                        false
-                    }
-                } else {
-                    if let Some(anchor) = self.select_anchor {
-                        self.cursor_idx = anchor.min(self.cursor_idx);
-                        self.select_anchor = None;
-                        self.all_selected = false;
-                        true
-                    } else if self.cursor_idx > 0 {
-                        self.cursor_idx -= 1;
-                        true
-                    } else {
-                        false
-                    }
-                }
-            }
-            Key::Named(NamedKey::ArrowRight) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                    if self.cursor_idx < self.edit_buffer.chars().count() {
-                        self.cursor_idx += 1;
-                        true
-                    } else {
-                        false
-                    }
-                } else {
-                    if let Some(anchor) = self.select_anchor {
-                        self.cursor_idx = anchor.max(self.cursor_idx);
-                        self.select_anchor = None;
-                        self.all_selected = false;
-                        true
-                    } else if self.cursor_idx < self.edit_buffer.chars().count() {
-                        self.cursor_idx += 1;
-                        true
-                    } else {
-                        false
-                    }
-                }
-            }
-            Key::Named(NamedKey::ArrowUp) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                } else {
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                }
-                if self.multiline {
-                    let char_width = self.font_size * 0.6;
-                    let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-                    let (lines, index_map) = self.wrap_text(max_chars);
-                    let (cursor_l, cursor_c) = index_map[self.cursor_idx.min(index_map.len() - 1)];
-                    if cursor_l > 0 {
-                        self.cursor_idx = self.map_2d_to_1d(&index_map, cursor_l - 1, cursor_c, lines.len() - 1);
-                    } else {
-                        self.cursor_idx = 0;
-                    }
-                } else {
-                    self.cursor_idx = 0;
-                }
-                true
-            }
-            Key::Named(NamedKey::ArrowDown) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                } else {
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                }
-                if self.multiline {
-                    let char_width = self.font_size * 0.6;
-                    let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-                    let (lines, index_map) = self.wrap_text(max_chars);
-                    let (cursor_l, cursor_c) = index_map[self.cursor_idx.min(index_map.len() - 1)];
-                    if cursor_l < lines.len() - 1 {
-                        self.cursor_idx = self.map_2d_to_1d(&index_map, cursor_l + 1, cursor_c, lines.len() - 1);
-                    } else {
-                        self.cursor_idx = self.edit_buffer.chars().count();
-                    }
-                } else {
-                    self.cursor_idx = self.edit_buffer.chars().count();
-                }
-                true
-            }
-            Key::Named(NamedKey::Home) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                } else {
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                }
-                self.cursor_idx = 0;
-                true
-            }
-            Key::Named(NamedKey::End) => {
-                let shift = event.shift;
-                if shift {
-                    if self.select_anchor.is_none() {
-                        self.select_anchor = Some(self.cursor_idx);
-                    }
-                } else {
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                }
-                self.cursor_idx = self.edit_buffer.chars().count();
-                true
-            }
-            Key::Named(NamedKey::Enter) => {
-                if self.multiline {
-                    let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-                    let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-                    let mut new_buf = String::new();
-                    let chars: Vec<char> = self.edit_buffer.chars().collect();
-                    for i in 0..start {
-                        new_buf.push(chars[i]);
-                    }
-                    new_buf.push('\n');
-                    for i in end..chars.len() {
-                        new_buf.push(chars[i]);
-                    }
-                    self.edit_buffer = new_buf;
-                    self.cursor_idx = start + 1;
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                } else {
-                    self.unfocus();
-                }
-                true
-            }
-            Key::Named(NamedKey::Escape) => {
-                self.editing = false;
-                self.edit_buffer = self.text.clone();
-                self.select_anchor = None;
-                self.all_selected = false;
-                true
-            }
-            Key::Character(ref ch_str) if control && (ch_str == "a" || ch_str == "A") => {
-                self.cursor_idx = self.edit_buffer.chars().count();
-                self.select_anchor = Some(0);
-                self.all_selected = true;
-                true
-            }
-            Key::Character(ref ch_str) if control && (ch_str == "c" || ch_str == "C") => {
-                self.copy_selection();
-                true
-            }
-            Key::Character(ref ch_str) if control && (ch_str == "x" || ch_str == "X") => {
-                self.cut_selection();
-                true
-            }
-            Key::Character(ref ch_str) if control && (ch_str == "v" || ch_str == "V") => {
-                if let Some(pasted) = clipboard::read_from_clipboard() {
-                    let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-                    let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-                    let mut new_buf = String::new();
-                    let chars: Vec<char> = self.edit_buffer.chars().collect();
-                    for i in 0..start {
-                        new_buf.push(chars[i]);
-                    }
-                    let inserted_count = pasted.chars().count();
-                    new_buf.push_str(&pasted);
-                    for i in end..chars.len() {
-                        new_buf.push(chars[i]);
-                    }
-                    self.edit_buffer = new_buf;
-                    self.cursor_idx = start + inserted_count;
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                } else {
-                    self.select_anchor = None;
-                    self.all_selected = false;
-                }
-                true
-            }
-            _ => {
-                if let Some(text) = &event.text {
-                    if !control {
-                        let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-                        let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-                        let mut new_buf = String::new();
-                        let chars: Vec<char> = self.edit_buffer.chars().collect();
-                        for i in 0..start {
-                            new_buf.push(chars[i]);
-                        }
-                        let inserted_count = text.chars().count();
-                        new_buf.push_str(text);
-                        for i in end..chars.len() {
-                            new_buf.push(chars[i]);
-                        }
-                        self.edit_buffer = new_buf;
-                        self.cursor_idx = start + inserted_count;
-                        self.select_anchor = None;
-                        self.all_selected = false;
-                        return true;
-                    }
-                }
-                false
-            }
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if self.disabled {
-            if self.draw_bg_border {
-                quads.push((self.base.x, self.base.y, self.base.w, self.base.h, [0.12, 0.12, 0.16, 1.0]));
-                quads.push((self.base.x + 1.0, self.base.y + 1.0, self.base.w - 2.0, self.base.h - 2.0, [0.06, 0.06, 0.08, 1.0]));
-            }
-            return quads;
-        }
-        
-        if self.draw_bg_border {
-            let bg_color = if self.editing {
-                [0.06, 0.10, 0.18, 1.0]
-            } else {
-                [0.08, 0.08, 0.12, 1.0]
-            };
-            let border_color = if self.editing {
-                [0.20, 0.50, 0.85, 1.0]
-            } else if self.base.hovered {
-                [0.25, 0.25, 0.35, 1.0]
-            } else {
-                [0.18, 0.18, 0.24, 1.0]
-            };
-            quads.push((self.base.x, self.base.y, self.base.w, self.base.h, border_color));
-            quads.push((self.base.x + 1.0, self.base.y + 1.0, self.base.w - 2.0, self.base.h - 2.0, bg_color));
-        }
-
-        if self.editing {
-            let char_width = self.font_size * 0.6;
-            let line_height = self.font_size * 1.333;
-            
-            let highlight_color = [0.20, 0.50, 0.85, 0.3];
-            let cursor_color = if self.draw_bg_border {
-                [0.80, 0.80, 0.85, 1.0]
-            } else {
-                [0.10, 0.10, 0.15, 1.0]
-            };
-
-            let start = self.select_anchor.unwrap_or(self.cursor_idx).min(self.cursor_idx);
-            let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
-
-            if self.multiline {
-                let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-                let (lines, index_map) = self.wrap_text(max_chars);
-
-                if start != end {
-                    let start_pos = index_map[start.min(index_map.len() - 1)];
-                    let end_pos = index_map[end.min(index_map.len() - 1)];
-                    
-                    for line_idx in start_pos.0..=end_pos.0 {
-                        let mut line_start_col = None;
-                        let mut line_end_col = None;
-                        for idx in start..end {
-                            if idx < index_map.len() {
-                                let (l, c) = index_map[idx];
-                                if l == line_idx {
-                                    if line_start_col.is_none() || c < line_start_col.unwrap() {
-                                        line_start_col = Some(c);
-                                    }
-                                    if line_end_col.is_none() || c > line_end_col.unwrap() {
-                                        line_end_col = Some(c);
-                                    }
-                                }
-                            }
-                        }
-                        if let (Some(sc), Some(ec)) = (line_start_col, line_end_col) {
-                            let highlight_x = self.base.x + 8.0 + (sc as f32 * char_width);
-                            let highlight_w = ((ec - sc + 1) as f32 * char_width);
-                            let highlight_y = self.base.y + 8.0 + (line_idx as f32 * line_height);
-                            quads.push((
-                                highlight_x,
-                                highlight_y,
-                                highlight_w,
-                                line_height,
-                                highlight_color,
-                            ));
-                        }
-                    }
-                }
-                
-                let caret_h = self.font_size * 1.15;
-                let (cursor_l, cursor_c) = index_map[self.cursor_idx.min(index_map.len() - 1)];
-                let cursor_x = self.base.x + 8.0 + (cursor_c as f32 * char_width);
-                let cursor_y = self.base.y + 8.0 + (cursor_l as f32 * line_height) + (line_height - caret_h) / 2.0;
-                quads.push((cursor_x, cursor_y, 1.5, caret_h, cursor_color));
-            } else {
-                let caret_h = self.font_size * 1.15;
-                let line_h = self.font_size * 1.333;
-                if start != end {
-                    let highlight_x = self.base.x + 8.0 + (start as f32 * char_width);
-                    let max_x = self.base.x + self.base.w - 6.0;
-                    let highlight_w = ((end - start) as f32 * char_width).min(max_x - highlight_x).max(0.0);
-                    quads.push((
-                        highlight_x,
-                        self.base.y + (self.base.h - line_h) / 2.0,
-                        highlight_w,
-                        line_h,
-                        highlight_color,
-                    ));
-                }
-
-                let cursor_x = self.base.x + 8.0 + (self.cursor_idx as f32 * char_width);
-                let max_cursor_x = self.base.x + self.base.w - 6.0;
-                let final_cursor_x = cursor_x.min(max_cursor_x);
-                let cursor_y = self.base.y + (self.base.h - caret_h) / 2.0;
-                quads.push((final_cursor_x, cursor_y, 1.5, caret_h, cursor_color));
-            }
-        }
-
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x + 4.0,
-                y: self.base.y - 14.0,
-                font_size: 11.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-        let mut val_text = if self.editing {
-            self.edit_buffer.clone()
-        } else {
-            self.text.clone()
-        };
-        if self.is_password {
-            val_text = "•".repeat(val_text.chars().count());
-        }
-
-        let label_color = if let Some(custom_color) = self.text_color {
-            custom_color
-        } else if self.disabled {
-            [0x53, 0x53, 0x5a]
-        } else if self.all_selected {
-            [0xff, 0xff, 0xff]
-        } else if self.editing {
-            [0xee, 0xee, 0xf5]
-        } else {
-            [0xcc, 0xcc, 0xd4]
-        };
-
-        if self.multiline {
-            let char_width = self.font_size * 0.6;
-            let line_height = self.font_size * 1.333;
-            let max_chars = (((self.base.w - 16.0) / char_width).floor() as usize).max(1);
-            let (lines, _) = self.wrap_text(max_chars);
-            for (line_idx, line_text) in lines.iter().enumerate() {
-                labels.push(TextLabel {
-                    text: line_text.clone(),
-                    x: self.base.x + 8.0,
-                    y: self.base.y + 8.0 + (line_idx as f32 * line_height) + (line_height - self.font_size) / 2.0,
-                    font_size: self.font_size,
-                    color: label_color,
-                });
-            }
-        } else {
-            labels.push(TextLabel {
-                text: val_text,
-                x: self.base.x + 8.0,
-                y: self.base.y + (self.base.h - self.font_size) / 2.0,
-                font_size: self.font_size,
-                color: label_color,
-            });
-        }
-        labels
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
-    fn clear_children(&mut self) { self.children.clear(); }
-}
-
-impl Drop for TextBox {
-    fn drop(&mut self) {
-        focus::clear_if_matches(self);
-    }
-}
-
-unsafe impl Send for TextBox {}
-unsafe impl Sync for TextBox {}
-
-use std::sync::OnceLock;
-static FONT_DB: OnceLock<resvg::usvg::fontdb::Database> = OnceLock::new();
-
-pub fn get_font_db() -> &'static resvg::usvg::fontdb::Database {
-    FONT_DB.get_or_init(|| {
-        let mut db = resvg::usvg::fontdb::Database::new();
-        db.load_system_fonts();
-        db
-    })
-}
-
-pub struct Plate {
-    pub base: WidgetBase,
-    pub dragging: bool,
-    pub drag_ox: f32,
-    pub drag_oy: f32,
-    pub drag_start_x: f32,
-    pub drag_start_y: f32,
-    pub bounds: Option<(f32, f32, f32, f32)>,
-    pub color: Option<[f32; 4]>,
-    pub curved_circle: Option<(f32, f32, f32)>,
-    pub network_opacity: f32,
-    pub blur: bool,
-    pub children: Vec<*mut (dyn Widget + 'static)>,
-    pub parent: Option<*mut (dyn Widget + 'static)>,
-    pub visible: bool,
-    pub column_layout: bool,
-}
-
-impl Plate {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: WidgetBase::new_rect(x, y, w, h),
-            dragging: false,
-            drag_ox: 0.0,
-            drag_oy: 0.0,
-            drag_start_x: 0.0,
-            drag_start_y: 0.0,
-            bounds: None,
-            color: None,
-            curved_circle: None,
-            network_opacity: 1.0,
-            blur: true,
-            children: Vec::new(),
-            parent: None,
-            visible: true,
-            column_layout: false,
-        }
-    }
-
-    pub fn with_color(mut self, color: [f32; 4]) -> Self {
-        self.color = Some(color);
-        self
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn with_blur(mut self, blur: bool) -> Self {
-        self.blur = blur;
-        self
-    }
-
-    pub fn set_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        self.bounds = Some((bx, by, bw, bh));
-    }
-}
-
-impl Widget for Plate {
-    fn base(&self) -> Option<&WidgetBase> { Some(&self.base) }
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> { Some(&mut self.base) }
-    fn is_plate(&self) -> bool { true }
-    fn rounded_corners(&self) -> (bool, bool, bool, bool) { (true, true, true, true) }
-    fn top_room(&self) -> f32 { 0.0 }
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> { None }
-
-    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
-        for &child_ptr in &self.children {
-            unsafe {
-                (*child_ptr).set_modifiers(ctrl, shift, alt);
-            }
-        }
-    }
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn color(&self) -> [f32; 4] {
-        let mut c = if let Some(c) = self.color {
-            c
-        } else if self.dragging {
-            colors::PANEL_DRAG
-        } else {
-            colors::PANEL_IDLE
-        };
-        c[3] *= self.network_opacity;
-        if self.blur {
-            c[3] = -c[3].abs();
-        }
-        c
-    }
-
-    fn set_network_opacity(&mut self, opacity: f32) {
-        self.network_opacity = opacity;
-    }
-
-    fn set_drag_bounds(&mut self, bx: f32, by: f32, bw: f32, bh: f32) {
-        self.bounds = Some((bx, by, bw, bh));
-    }
-
-    fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
-        self.curved_circle = circle;
-    }
-
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some((cx, cy, r)) = self.curved_circle {
-            let dx = px - cx;
-            let dy = py - cy;
-            return dx * dx + dy * dy <= r * r;
-        }
-        
-        let (x, y, w, h) = self.rect();
-        if px < x || px >= x + w || py < y || py >= y + h {
-            return false;
-        }
-        
-        let r = 12.0f32.min(w * 0.5).min(h * 0.5);
-        if r <= 0.1 {
-            return true;
-        }
-        
-        // Check corners
-        if px < x + r && py < y + r {
-            let dx = px - (x + r);
-            let dy = py - (y + r);
-            return dx * dx + dy * dy <= r * r;
-        }
-        if px >= x + w - r && py < y + r {
-            let dx = px - (x + w - r);
-            let dy = py - (y + r);
-            return dx * dx + dy * dy <= r * r;
-        }
-        if px >= x + w - r && py >= y + h - r {
-            let dx = px - (x + w - r);
-            let dy = py - (y + h - r);
-            return dx * dx + dy * dy <= r * r;
-        }
-        if px < x + r && py >= y + h - r {
-            let dx = px - (x + r);
-            let dy = py - (y + h - r);
-            return dx * dx + dy * dy <= r * r;
-        }
-        
-        true
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        if let Some(b) = self.base_mut() {
-            b.x = x;
-            b.y = y;
-            b.w = w;
-            b.h = h;
-        }
-
-        if !self.visible {
-            return;
-        }
-
-        let padding_x = 20.0;
-        let padding_y = 20.0;
-        let left_x = x + padding_x;
-        let available_w = (w - 2.0 * padding_x).max(1.0);
-        let start_y = y + padding_y;
-        let available_h = (h - 2.0 * padding_y).max(1.0);
-
-        let center_x = left_x + available_w / 2.0;
-        let center_y = start_y + available_h / 2.0;
-        let aspect_ratio = available_w / available_h;
-
-        let mut active_widgets = Vec::new();
-        for &w_ptr in &self.children {
-            let w = unsafe { &*w_ptr };
-            if !w.layout_ignore() {
-                active_widgets.push(w_ptr);
-            }
-        }
-
-        if self.column_layout {
-            let mut current_y = start_y;
-            let spacing = 12.0;
-            for &w_ptr in &active_widgets {
-                let w = unsafe { &mut *w_ptr };
-                let (_, _, ww, wh) = w.rect();
-                let use_w = if ww > 0.0 { ww.min(available_w) } else { available_w };
-                let top = w.top_room();
-                let use_h = if wh > 0.0 { wh } else { 24.0 + top };
-
-                w.set_rect(left_x, current_y, use_w, use_h);
-                current_y += use_h + spacing;
-            }
-        } else {
-            let mut total_diagonal = 0.0;
-            let mut count = 0;
-            for &w_ptr in &active_widgets {
-                let w = unsafe { &*w_ptr };
-                let (_, _, ww, wh) = w.rect();
-                let use_w = if ww > 0.0 { ww.min(available_w) } else { available_w };
-                let use_h = if wh > 0.0 { wh } else { 50.0 };
-                total_diagonal += (use_w * use_w + use_h * use_h).sqrt();
-                count += 1;
-            }
-            let avg_diagonal = if count > 0 { total_diagonal / count as f32 } else { 100.0 };
-            let base_spacing = (avg_diagonal * 0.55).max(60.0);
-
-            for (i, &w_ptr) in active_widgets.iter().enumerate() {
-                let w = unsafe { &mut *w_ptr };
-                let (_, _, ww, wh) = w.rect();
-                let use_w = if ww > 0.0 { ww.min(available_w) } else { available_w };
-                let use_h = if wh > 0.0 { wh } else { 50.0 };
-
-                if i == 0 {
-                    w.set_rect(center_x - use_w / 2.0, center_y - use_h / 2.0, use_w, use_h);
-                } else {
-                    let mut ring = 1;
-                    let mut ring_start = 1;
-                    let mut placed = false;
-                    while !placed {
-                        let ring_capacity = ring * 6;
-                        if i < ring_start + ring_capacity {
-                            let pos_in_ring = i - ring_start;
-                            let angle = (pos_in_ring as f32) * (2.0 * std::f32::consts::PI / ring_capacity as f32);
-                            let radius = (ring as f32) * base_spacing;
-
-                            let x_offset = radius * angle.cos() * aspect_ratio;
-                            let y_offset = radius * angle.sin();
-
-                            w.set_rect(
-                                center_x + x_offset - use_w / 2.0,
-                                center_y + y_offset - use_h / 2.0,
-                                use_w,
-                                use_h,
-                            );
-                            placed = true;
-                        } else {
-                            ring_start += ring_capacity;
-                            ring += 1;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> {
-        self.parent
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) {
-        self.parent = parent;
-    }
-
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> {
-        self.children.clone()
-    }
-
-    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) {
-        self.children.push(child);
-    }
-
-    fn clear_children(&mut self) {
-        self.children.clear();
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        let (px, py, pw, ph) = self.rect();
-        quads.push((px, py, pw, ph, self.color()));
-
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            let c = widget.color();
-            if c[3] > 0.0 {
-                let (wx, wy, ww, wh) = widget.rect();
-                quads.push((wx, wy, ww, wh, c));
-            }
-            quads.extend(widget.all_quads());
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut labels = Vec::new();
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x: self.base.x,
-                y: self.base.y - 18.0,
-                font_size: 12.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            labels.extend(widget.text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut result = Vec::new();
-        if let Some(ref label) = self.base.label {
-            result.push((
-                TextLabel {
-                    text: label.clone(),
-                    x: self.base.x,
-                    y: self.base.y - 18.0,
-                    font_size: 12.0,
-                    color: [0x83, 0x83, 0x8a],
-                },
-                None,
-            ));
-        }
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            result.extend(widget.text_labels_with_bounds());
-        }
-        result
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let mut changed = false;
-        for &widget_ptr in &self.children {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.is_dragging() {
-                if widget.drag_update(px, py) {
-                    changed = true;
-                }
-            } else if widget.cursor_moved(px, py) {
-                changed = true;
-            }
-        }
-        changed
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        for &widget_ptr in self.children.iter().rev() {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.popover_rect().is_some() {
-                if widget.mouse_input(button, state, px, py) {
-                    return true;
-                }
-            }
-        }
-        for &widget_ptr in self.children.iter().rev() {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.mouse_input(button, state, px, py) {
-                return true;
-            }
-            if state == ElementState::Pressed && !widget.hit_test(px, py) {
-                widget.unfocus();
-            }
-        }
-
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                if self.hit_test(px, py) {
-                    self.drag_begin(px, py);
-                    return true;
-                }
-            }
-            ElementState::Released => {
-                if self.dragging { self.drag_end(); return true; }
-            }
-        }
-        false
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if !self.visible {
-            return false;
-        }
-        for &widget_ptr in &self.children {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.keyboard_input(event) {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        for &widget_ptr in self.children.iter().rev() {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.mouse_wheel(delta, px, py) {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        if !self.visible {
-            return None;
-        }
-        for &widget_ptr in self.children.iter().rev() {
-            let widget = unsafe { &*widget_ptr };
-            if let Some(r) = widget.popover_rect() {
-                return Some(r);
-            }
-        }
-        None
-    }
-
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        if !self.visible {
-            return;
-        }
-        for &widget_ptr in self.children.iter().rev() {
-            let widget = unsafe { &*widget_ptr };
-            widget.render_popover(pc);
-        }
-    }
-
-    fn tick(&mut self, dt: f32) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let mut changed = false;
-        for &widget_ptr in &self.children {
-            let widget = unsafe { &mut *widget_ptr };
-            if widget.tick(dt) {
-                changed = true;
-            }
-        }
-        changed
-    }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        let nx = px - self.drag_ox;
-        let ny = py - self.drag_oy;
-        let (nx, ny) = if let Some((bx, by, bw, bh)) = self.bounds {
-            (nx.clamp(bx, bx + bw - self.base.w), ny.clamp(by, by + bh - self.base.h))
-        } else {
-            (nx, ny)
-        };
-        if (nx - self.base.x).abs() > 0.01 || (ny - self.base.y).abs() > 0.01 {
-            let dx = nx - self.base.x;
-            let dy = ny - self.base.y;
-            self.base.x = nx;
-            self.base.y = ny;
-            
-            for &child_ptr in &self.children {
-                unsafe {
-                    let (cx, cy, cw, ch) = (*child_ptr).rect();
-                    (*child_ptr).set_rect(cx + dx, cy + dy, cw, ch);
-                }
-            }
-            return true;
-        }
-        false
-    }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        self.dragging = true;
-        self.drag_ox = px - self.base.x;
-        self.drag_oy = py - self.base.y;
-        self.drag_start_x = self.base.x;
-        self.drag_start_y = self.base.y;
-    }
-
-    fn drag_end(&mut self) { self.dragging = false; }
-}
-
-unsafe impl Send for Plate {}
-unsafe impl Sync for Plate {}
-
-pub struct Paginator {
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
-    hovered: bool,
-    pub sidebar_menu: MenuBar,
-    pub plates: Vec<Plate>,
-    pub selected_page: usize,
-    pub page_changed: bool,
-    pub sidebar_label: Option<String>,
-    pub tab_text_quads: Vec<Vec<(f32, f32, f32, f32, [f32; 4])>>,
-    pub sidebar_scroll_y: f32,
-    pub scale_factor: f32,
-    pub sidebar_mode: bool,
-    pub page_hidden: bool,
-    pub sidebar_w: f32,
-    pub pages: Vec<String>,
-    pub tabs_at_top: bool,
-    pub tab_y_offset: f32,
-    pub tabs_rotated: bool,
-    pub hovered_tab: Option<usize>,
-    pub pressed_tab: Option<usize>,
-    pub target_y: f32,
-    pub target_x: f32,
-    pub current_y: Option<f32>,
-    pub current_x: Option<f32>,
-    parent: Option<*mut (dyn Widget + 'static)>,
-}
-
-impl Paginator {
-    pub fn sidebar_w(&self) -> f32 {
-        self.sidebar_w
-    }
-
-    pub fn new(sidebar_w: f32, pages: Vec<String>) -> Self {
-        let num_pages = pages.len();
-        
-        let mut sidebar_menu = MenuBar::new(0.0, 0.0, sidebar_w, 0.0)
-            .with_vertical(true);
-        for page in &pages {
-            sidebar_menu = sidebar_menu.with_item(page, &[]);
-        }
-
-        let mut plates = Vec::new();
-        for _ in 0..num_pages {
-            let mut plate = Plate::new(0.0, 0.0, 0.0, 0.0);
-            plate.visible = false;
-            plates.push(plate);
-        }
-        if num_pages > 0 {
-            plates[0].visible = true;
-            sidebar_menu.menus[0].set_selected(true);
-        }
-
-        let mut pag = Self {
-            x: 0.0,
-            y: 0.0,
-            w: 0.0,
-            h: 0.0,
-            hovered: false,
-            sidebar_menu,
-            plates,
-            selected_page: 0,
-            page_changed: false,
-            sidebar_label: None,
-            tab_text_quads: Vec::new(),
-            sidebar_scroll_y: 0.0,
-            scale_factor: 1.0,
-            sidebar_mode: true,
-            page_hidden: false,
-            sidebar_w,
-            pages,
-            tabs_at_top: false,
-            tab_y_offset: 10.0,
-            tabs_rotated: true,
-            hovered_tab: None,
-            pressed_tab: None,
-            target_y: 10.0,
-            target_x: 0.0,
-            current_y: Some(10.0),
-            current_x: Some(0.0),
-            parent: None,
-        };
-        pag.update_target_pos();
-        pag
-    }
-
-    pub fn tab_rect(&self, idx: usize) -> (f32, f32, f32, f32) {
-        if idx >= self.pages.len() {
-            return (0.0, 0.0, 0.0, 0.0);
-        }
-        if self.tabs_at_top {
-            let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-            (self.x + idx as f32 * tab_w, self.y, tab_w, 40.0)
-        } else if self.tabs_rotated {
-            let (tab_w, tab_h) = self.vertical_tab_size();
-            let spacing = 10.0;
-            (
-                self.x + (self.sidebar_w - tab_w) / 2.0,
-                self.y + self.tab_y_offset + idx as f32 * (tab_h + spacing) - self.sidebar_scroll_y,
-                tab_w,
-                tab_h,
-            )
-        } else {
-            let bw = self.sidebar_w - 10.0;
-            (self.x + 5.0, self.y + self.tab_y_offset + idx as f32 * 50.0 - self.sidebar_scroll_y, bw, 40.0)
-        }
-    }
-
-    pub fn tab_size(&self, idx: usize) -> (f32, f32) {
-        let r = self.tab_rect(idx);
-        (r.2, r.3)
-    }
-
-    pub fn with_column_layout(mut self, enabled: bool) -> Self {
-        for plate in &mut self.plates {
-            plate.column_layout = enabled;
-        }
-        self
-    }
-
-    pub fn with_sidebar_mode(mut self, enabled: bool) -> Self {
-        self.sidebar_mode = enabled;
-        self
-    }
-
-    pub fn with_sidebar_label(mut self, label: &str) -> Self {
-        self.sidebar_label = Some(label.to_string());
-        self
-    }
-
-    pub fn sidebar_label_height(&self) -> f32 {
-        if self.sidebar_label.is_some() {
-            24.0
-        } else {
-            0.0
-        }
-    }
-
-    pub fn is_page_hidden(&self) -> bool {
-        self.page_hidden
-    }
-
-    pub fn set_page_hidden(&mut self, hidden: bool) {
-        self.page_hidden = hidden;
-    }
-
-    pub fn set_sidebar_mode(&mut self, enabled: bool) {
-        self.sidebar_mode = enabled;
-    }
-
-    pub fn add_widget_to_page(&mut self, page_idx: usize, widget: *mut (dyn Widget + 'static)) {
-        if page_idx < self.plates.len() {
-            self.plates[page_idx].add_child(widget);
-            unsafe {
-                (*widget).set_parent(Some(&mut self.plates[page_idx] as *mut _));
-            }
-        }
-    }
-
-    pub fn clear_page_widgets(&mut self, page_idx: usize) {
-        if page_idx < self.plates.len() {
-            self.plates[page_idx].clear_children();
-        }
-    }
-
-    pub fn with_tabs_at_top(mut self, top: bool) -> Self {
-        self.tabs_at_top = top;
-        self.update_target_pos();
-        self
-    }
-
-    pub fn with_tab_y_offset(mut self, offset: f32) -> Self {
-        self.tab_y_offset = offset;
-        if self.current_y == Some(10.0) {
-            self.current_y = Some(offset);
-        }
-        self.update_target_pos();
-        self
-    }
-
-    pub fn with_tabs_rotated(mut self, rotated: bool) -> Self {
-        self.tabs_rotated = rotated;
-        self.update_target_pos();
-        self
-    }
-
-    pub fn selected_page(&self) -> usize {
-        self.selected_page
-    }
-
-    pub fn set_selected_page(&mut self, page: usize) {
-        if page < self.plates.len() {
-            if self.selected_page != page {
-                self.plates[self.selected_page].visible = false;
-                self.selected_page = page;
-                self.plates[self.selected_page].visible = true;
-                self.update_target_pos();
-                
-                // Update MenuBar focus/selection
-                for (i, menu) in self.sidebar_menu.menus.iter_mut().enumerate() {
-                    menu.set_selected(i == page);
-                }
-            }
-        }
-    }
-
-    pub fn set_pages(&mut self, pages: Vec<String>) {
-        self.pages = pages.clone();
-        let num_pages = pages.len();
-        
-        let mut sidebar_menu = MenuBar::new(0.0, 0.0, self.sidebar_w, 0.0)
-            .with_vertical(true);
-        for page in &pages {
-            sidebar_menu = sidebar_menu.with_item(page, &[]);
-        }
-        self.sidebar_menu = sidebar_menu;
-
-        let mut plates = Vec::new();
-        for _ in 0..num_pages {
-            let mut plate = Plate::new(0.0, 0.0, 0.0, 0.0);
-            plate.visible = false;
-            plates.push(plate);
-        }
-        self.plates = plates;
-        if self.selected_page >= num_pages {
-            self.selected_page = 0;
-        }
-        if !self.plates.is_empty() {
-            self.plates[self.selected_page].visible = true;
-            self.sidebar_menu.menus[self.selected_page].set_selected(true);
-        }
-        self.update_target_pos();
-    }
-
-    pub fn set_scale_factor(&mut self, scale: f32) {
-        self.scale_factor = scale;
-    }
-
-    pub fn vertical_tab_size(&self) -> (f32, f32) {
-        if self.tabs_rotated {
-            ((self.sidebar_w - 16.0).clamp(24.0, 120.0), 120.0)
-        } else {
-            (self.sidebar_w - 10.0, 40.0)
-        }
-    }
-
-    fn total_sidebar_height(&self) -> f32 {
-        let (_, tab_h) = self.vertical_tab_size();
-        let spacing = 10.0;
-        let step = if self.tabs_rotated { tab_h + spacing } else { 50.0 };
-        self.tab_y_offset + self.pages.len() as f32 * step - spacing
-    }
-
-    fn update_target_pos(&mut self) {
-        if self.tabs_at_top {
-            let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-            self.target_x = self.selected_page as f32 * tab_w;
-            if self.current_x.is_none() {
-                self.current_x = Some(self.target_x);
-            }
-        } else if self.tabs_rotated {
-            let (_, tab_h) = self.vertical_tab_size();
-            let spacing = 10.0;
-            let target = self.tab_y_offset + self.selected_page as f32 * (tab_h + spacing);
-            self.target_y = target;
-            if self.current_y.is_none() {
-                self.current_y = Some(target);
-            }
-        } else {
-            let target = self.tab_y_offset + self.selected_page as f32 * 50.0;
-            self.target_y = target;
-            if self.current_y.is_none() {
-                self.current_y = Some(target);
-            }
-        }
-        self.generate_tab_quads();
-    }
-
-    fn generate_tab_quads(&mut self) {
-        self.tab_text_quads.clear();
-        let (tab_w, tab_h) = self.vertical_tab_size();
-        let active_color = colors::paginator_tab_label_color();
-        let active_srgb = colors::to_srgb(active_color);
-        let active_r = (active_srgb[0] * 255.0) as u8;
-        let active_g = (active_srgb[1] * 255.0) as u8;
-        let active_b = (active_srgb[2] * 255.0) as u8;
-        let inactive_r = (active_r as f32 * 0.78) as u8;
-        let inactive_g = (active_g as f32 * 0.78) as u8;
-        let inactive_b = (active_b as f32 * 0.78) as u8;
-
-        for (i, page_name) in self.pages.iter().enumerate() {
-            let color = if self.selected_page == i {
-                [active_r, active_g, active_b]
-            } else {
-                [inactive_r, inactive_g, inactive_b]
-            };
-            let hex_color = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
-
-            let trimmed = page_name.trim();
-            let has_icon = trimmed.find(' ').is_some();
-            let label_text = if let Some(space_idx) = trimmed.find(' ') {
-                trimmed.split_at(space_idx).1.trim()
-            } else {
-                trimmed
-            };
-
-            let w_px = tab_w as u32;
-            let h_px = if has_icon { 80 } else { 120 };
-
-            if w_px == 0 || h_px == 0 {
-                self.tab_text_quads.push(Vec::new());
-                continue;
-            }
-
-            let svg_data = format!(
-                r##"<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">
-  <text x="{}" y="{}" font-family="sans-serif" font-size="12" fill="{}" text-anchor="middle" dominant-baseline="middle" transform="rotate(-90 {} {})">{}</text>
-</svg>"##,
-                w_px, h_px,
-                w_px as f32 / 2.0, h_px as f32 / 2.0,
-                hex_color,
-                w_px as f32 / 2.0, h_px as f32 / 2.0,
-                label_text
-            );
-
-            let opt = resvg::usvg::Options::default();
-            let fontdb = get_font_db();
-            
-            let mut page_quads = Vec::new();
-            if let Ok(tree) = resvg::usvg::Tree::from_data(svg_data.as_bytes(), &opt, fontdb) {
-                if let Some(mut pixmap) = resvg::tiny_skia::Pixmap::new(w_px, h_px) {
-                    resvg::render(&tree, resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut());
-                    let pixels = pixmap.data();
-                    for row in 0..h_px {
-                        for col in 0..w_px {
-                            let idx = ((row * w_px + col) * 4) as usize;
-                            if idx + 3 < pixels.len() {
-                                let a = pixels[idx + 3] as f32 / 255.0;
-                                if a > 0.0 {
-                                    let r = pixels[idx] as f32 / 255.0;
-                                    let g = pixels[idx + 1] as f32 / 255.0;
-                                    let b = pixels[idx + 2] as f32 / 255.0;
-                                    page_quads.push((
-                                        col as f32,
-                                        row as f32,
-                                        1.0,
-                                        1.0,
-                                        [r, g, b, a],
-                                    ));
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            self.tab_text_quads.push(page_quads);
-        }
-    }
-}
-
-impl Widget for Paginator {
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        (self.x, self.y, self.w, self.h)
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.x = x;
-        self.y = y;
-        self.w = w;
-        self.h = h;
-        self.update_target_pos();
-
-        let self_ptr = self as *mut Paginator;
-        self.sidebar_menu.set_parent(Some(self_ptr));
-        for plate in &mut self.plates {
-            plate.set_parent(Some(self_ptr));
-        }
-
-        let tabs_at_top = self.tabs_at_top;
-        if tabs_at_top {
-            self.sidebar_menu.set_rect(self.x, self.y, self.w, 40.0);
-            for plate in &mut self.plates {
-                plate.set_rect(self.x, self.y + 40.0, self.w, (self.h - 40.0).max(0.0));
-            }
-        } else {
-            let sidebar_w = self.sidebar_w;
-            self.sidebar_menu.set_rect(self.x, self.y, sidebar_w, self.h);
-            for plate in &mut self.plates {
-                plate.set_rect(self.x + sidebar_w, self.y, (self.w - sidebar_w).max(0.0), self.h);
-            }
-        }
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.0, 0.0, 0.0, 0.0]
-    }
-
-    fn set_hovered(&mut self, v: bool) {
-        self.hovered = v;
-    }
-
-    fn hovered(&self) -> bool {
-        self.hovered
-    }
-
-    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-        if let Some(i) = self.hovered_tab {
-            if self.tabs_at_top {
-                let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-                let bx = self.x + i as f32 * tab_w + 2.0;
-                let by = self.y + 2.0;
-                let bw = tab_w - 4.0;
-                let bh = 40.0 - 4.0;
-                let scroll_offset = hover_animation::get_scroll_offset();
-                Some((bx, by + scroll_offset, bw, bh, colors::HIGHLIGHT_SECONDARY))
-            } else {
-                let (bx, by, bw, bh) = if self.tabs_rotated {
-                    let (tab_w, tab_h) = self.vertical_tab_size();
-                    let spacing = 10.0;
-                    (
-                        self.x + (self.sidebar_w - tab_w) / 2.0,
-                        self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y,
-                        tab_w,
-                        tab_h,
-                    )
-                } else {
-                    let bw = self.sidebar_w - 10.0;
-                    (
-                        self.x + 5.0,
-                        self.y + self.tab_y_offset + i as f32 * 50.0 - self.sidebar_scroll_y,
-                        bw,
-                        40.0,
-                    )
-                };
-                let scroll_offset = hover_animation::get_scroll_offset();
-                let qy = by + scroll_offset;
-                let qh = bh;
-
-                let min_y = self.y;
-                let max_y = self.y + self.h;
-                let ry1 = qy.max(min_y);
-                let ry2 = (qy + qh).min(max_y);
-                let rh = ry2 - ry1;
-                if rh > 0.0 {
-                    Some((bx, ry1, bw, rh, colors::HIGHLIGHT_SECONDARY))
-                } else {
-                    None
-                }
-            }
-        } else {
-            None
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        if self.tabs_rotated {
-            quads.push((self.x, self.y, self.sidebar_w, self.h, colors::sidebar_bg_color()));
-            
-            let (tab_w, tab_h) = self.vertical_tab_size();
-            let spacing = 10.0;
-            let min_y = self.y;
-            let max_y = self.y + self.h;
-
-            for (i, page_name) in self.pages.iter().enumerate() {
-                let bx = self.x + (self.sidebar_w - tab_w) / 2.0;
-                let by = self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y;
-
-                let trimmed = page_name.trim();
-                let has_icon = trimmed.find(' ').is_some();
-                let y_offset = if has_icon { 40.0 } else { 0.0 };
-
-                if i < self.tab_text_quads.len() {
-                    for &(qx, qy, qw, qh, qc) in &self.tab_text_quads[i] {
-                        let absolute_x = bx + qx;
-                        let absolute_y = by + y_offset + qy;
-                        
-                        let ry1 = absolute_y.max(min_y);
-                        let ry2 = (absolute_y + qh).min(max_y);
-                        let rh = ry2 - ry1;
-                        if rh > 0.0 {
-                            quads.push((absolute_x, ry1, qw, rh, qc));
-                        }
-                    }
-                }
-            }
-        } else {
-            quads.extend(self.sidebar_menu.extra_quads());
-        }
-
-        if self.selected_page < self.plates.len() {
-            quads.extend(self.plates[self.selected_page].extra_quads());
-        }
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        if self.tabs_rotated {
-            let (tab_w, tab_h) = self.vertical_tab_size();
-            let spacing = 10.0;
-            let active_color = colors::paginator_tab_label_color();
-            let active_srgb = colors::to_srgb(active_color);
-            let active_r = (active_srgb[0] * 255.0) as u8;
-            let active_g = (active_srgb[1] * 255.0) as u8;
-            let active_b = (active_srgb[2] * 255.0) as u8;
-            let inactive_r = (active_r as f32 * 0.78) as u8;
-            let inactive_g = (active_g as f32 * 0.78) as u8;
-            let inactive_b = (active_b as f32 * 0.78) as u8;
-
-            for (i, page_name) in self.pages.iter().enumerate() {
-                let color = if self.selected_page == i {
-                    [active_r, active_g, active_b]
-                } else {
-                    [inactive_r, inactive_g, inactive_b]
-                };
-                let bx = self.x + (self.sidebar_w - tab_w) / 2.0;
-                let by = self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y;
-                let bw = tab_w;
-
-                let trimmed = page_name.trim();
-                let has_icon = trimmed.find(' ').is_some();
-                if has_icon {
-                    if let Some(space_idx) = trimmed.find(' ') {
-                        let (icon, _) = trimmed.split_at(space_idx);
-                        let icon = icon.trim();
-                        if !icon.is_empty() {
-                            let icon_font_size = 14.0;
-                            let est_icon_w = TextLabel::estimate_width(icon, icon_font_size);
-                            let icon_y = by + 12.0;
-                            if icon_y >= self.y && icon_y + icon_font_size <= self.y + self.h {
-                                labels.push(TextLabel {
-                                    text: icon.to_string(),
-                                    x: bx + (bw - est_icon_w) / 2.0,
-                                    y: icon_y,
-                                    font_size: icon_font_size,
-                                    color,
-                                });
-                            }
-                        }
-                    }
-                }
-            }
-        } else {
-            labels.extend(self.sidebar_menu.text_labels());
-        }
-
-        if self.selected_page < self.plates.len() {
-            labels.extend(self.plates[self.selected_page].text_labels());
-        }
-        labels
-    }
-
-    fn text_labels_with_bounds(&self) -> Vec<(TextLabel, Option<[f32; 4]>)> {
-        let mut labels = Vec::new();
-        for l in self.text_labels() {
-            labels.push((l, None));
-        }
-        labels
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let mut changed = false;
-        let was_hovered_tab = self.hovered_tab;
-        self.hovered_tab = None;
-        for i in 0..self.pages.len() {
-            let (bx, by, bw, bh) = if self.tabs_at_top {
-                let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-                (self.x + i as f32 * tab_w, self.y, tab_w, 40.0)
-            } else if self.tabs_rotated {
-                let (tab_w, tab_h) = self.vertical_tab_size();
-                let spacing = 10.0;
-                (
-                    self.x + (self.sidebar_w - tab_w) / 2.0,
-                    self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y,
-                    tab_w,
-                    tab_h,
-                )
-            } else {
-                let bw = self.sidebar_w - 10.0;
-                (self.x + 5.0, self.y + self.tab_y_offset + i as f32 * 50.0 - self.sidebar_scroll_y, bw, 40.0)
-            };
-            if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
-                self.hovered_tab = Some(i);
-                break;
-            }
-        }
-        if was_hovered_tab != self.hovered_tab {
-            changed = true;
-        }
-
-        if self.sidebar_menu.cursor_moved(px, py) {
-            changed = true;
-        }
-        if self.selected_page < self.plates.len() {
-            if self.plates[self.selected_page].cursor_moved(px, py) {
-                changed = true;
-            }
-        }
-
-        changed
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if self.selected_page < self.plates.len() {
-            if self.plates[self.selected_page].popover_rect().is_some() {
-                if self.plates[self.selected_page].mouse_input(button, state, px, py) {
-                    return true;
-                }
-            }
-        }
-
-        let mut clicked_tab = false;
-        if button == MouseButton::Left {
-            match state {
-                ElementState::Pressed => {
-                    self.pressed_tab = None;
-                    for i in 0..self.pages.len() {
-                        let (bx, by, bw, bh) = if self.tabs_at_top {
-                            let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-                            (self.x + i as f32 * tab_w, self.y, tab_w, 40.0)
-                        } else if self.tabs_rotated {
-                            let (tab_w, tab_h) = self.vertical_tab_size();
-                            let spacing = 10.0;
-                            (
-                                self.x + (self.sidebar_w - tab_w) / 2.0,
-                                self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y,
-                                tab_w,
-                                tab_h,
-                            )
-                        } else {
-                            let bw = self.sidebar_w - 10.0;
-                            (self.x + 5.0, self.y + self.tab_y_offset + i as f32 * 50.0 - self.sidebar_scroll_y, bw, 40.0)
-                        };
-                        if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
-                            self.pressed_tab = Some(i);
-                            clicked_tab = true;
-                            break;
-                        }
-                    }
-                }
-                ElementState::Released => {
-                    if let Some(i) = self.pressed_tab.take() {
-                        let (bx, by, bw, bh) = if self.tabs_at_top {
-                            let tab_w = if self.pages.is_empty() { 0.0 } else { self.w / self.pages.len() as f32 };
-                            (self.x + i as f32 * tab_w, self.y, tab_w, 40.0)
-                        } else if self.tabs_rotated {
-                            let (tab_w, tab_h) = self.vertical_tab_size();
-                            let spacing = 10.0;
-                            (
-                                self.x + (self.sidebar_w - tab_w) / 2.0,
-                                self.y + self.tab_y_offset + i as f32 * (tab_h + spacing) - self.sidebar_scroll_y,
-                                tab_w,
-                                tab_h,
-                            )
-                        } else {
-                            let bw = self.sidebar_w - 10.0;
-                            (self.x + 5.0, self.y + self.tab_y_offset + i as f32 * 50.0 - self.sidebar_scroll_y, bw, 40.0)
-                        };
-                        if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
-                            if self.selected_page != i {
-                                self.set_selected_page(i);
-                                self.page_changed = true;
-                            }
-                            clicked_tab = true;
-                        }
-                    }
-                }
-            }
-        }
-
-        if clicked_tab {
-            return true;
-        }
-
-        if self.sidebar_menu.mouse_input(button, state, px, py) {
-            return true;
-        }
-
-        if self.selected_page < self.plates.len() {
-            if self.plates[self.selected_page].mouse_input(button, state, px, py) {
-                return true;
-            }
-        }
-
-        false
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
-        if self.sidebar_menu.keyboard_input(event) {
-            return true;
-        }
-        if self.selected_page < self.plates.len() {
-            if self.plates[self.selected_page].keyboard_input(event) {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if !self.tabs_at_top {
-            let bx = self.x;
-            let by = self.y;
-            let bw = self.sidebar_w;
-            let bh = self.h;
-            if px >= bx && px <= bx + bw && py >= by && py <= by + bh {
-                let scroll_speed = 24.0;
-                let dy = match delta {
-                    MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
-                    MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
-                };
-                let old_scroll = self.sidebar_scroll_y;
-                let max_scroll = (self.total_sidebar_height() - self.h).max(0.0);
-                self.sidebar_scroll_y = (self.sidebar_scroll_y + dy).clamp(0.0, max_scroll);
-                if (self.sidebar_scroll_y - old_scroll).abs() > 0.01 {
-                    self.update_target_pos();
-                    return true;
-                }
-            }
-        }
-
-        if self.sidebar_menu.mouse_wheel(delta, px, py) {
-            return true;
-        }
-
-        if self.selected_page < self.plates.len() {
-            if self.plates[self.selected_page].mouse_wheel(delta, px, py) {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        if let Some(r) = self.sidebar_menu.popover_rect() {
-            return Some(r);
-        }
-        if self.selected_page < self.plates.len() {
-            if let Some(r) = self.plates[self.selected_page].popover_rect() {
-                return Some(r);
-            }
-        }
-        None
-    }
-
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        self.sidebar_menu.render_popover(pc);
-        if self.selected_page < self.plates.len() {
-            self.plates[self.selected_page].render_popover(pc);
-        }
-    }
-
-    fn take_click(&mut self) -> bool {
-        if self.page_changed {
-            self.page_changed = false;
-            true
-        } else {
-            false
-        }
-    }
-
-    fn value(&self) -> i32 {
-        self.selected_page as i32
-    }
-
-    fn tick(&mut self, dt: f32) -> bool {
-        let mut changed = false;
-        if let Some(current) = self.current_y {
-            let diff = self.target_y - current;
-            if diff.abs() > 0.1 {
-                let decay = 15.0;
-                let next = current + diff * (1.0 - (-decay * dt).exp());
-                self.current_y = Some(next);
-                changed = true;
-            } else {
-                self.current_y = Some(self.target_y);
-            }
-        }
-        if let Some(current) = self.current_x {
-            let diff = self.target_x - current;
-            if diff.abs() > 0.1 {
-                let decay = 15.0;
-                let next = current + diff * (1.0 - (-decay * dt).exp());
-                self.current_x = Some(next);
-                changed = true;
-            } else {
-                self.current_x = Some(self.target_x);
-            }
-        }
-
-        let self_ptr = self as *mut Paginator;
-        self.sidebar_menu.set_parent(Some(self_ptr));
-        for plate in &mut self.plates {
-            plate.set_parent(Some(self_ptr));
-        }
-
-        if self.sidebar_menu.tick(dt) {
-            changed = true;
-        }
-
-        for plate in &mut self.plates {
-            if plate.tick(dt) {
-                changed = true;
-            }
-        }
-
-        changed
-    }
-
-    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> {
-        self.parent
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) {
-        self.parent = parent;
-    }
-
-    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> {
-        let mut list = Vec::new();
-        list.push(&self.sidebar_menu as *const dyn Widget as *mut dyn Widget);
-        for plate in &self.plates {
-            list.push(plate as *const dyn Widget as *mut dyn Widget);
-        }
-        list
-    }
-
-    fn add_child(&mut self, _child: *mut (dyn Widget + 'static)) {}
-    fn clear_children(&mut self) {}
-
-    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
-        self.sidebar_menu.set_modifiers(ctrl, shift, alt);
-        if self.selected_page < self.plates.len() {
-            self.plates[self.selected_page].set_modifiers(ctrl, shift, alt);
-        }
-    }
-    fn menu_names(&self) -> Vec<String> {
-        self.pages.clone()
-    }
-    fn selected_page(&self) -> usize {
-        self.selected_page()
-    }
-    fn set_selected_page(&mut self, page: usize) {
-        self.set_selected_page(page);
-    }
-    fn is_page_hidden(&self) -> bool {
-        self.is_page_hidden()
-    }
-    fn set_page_hidden(&mut self, hidden: bool) {
-        self.set_page_hidden(hidden);
-    }
-    fn set_pages(&mut self, pages: Vec<String>) {
-        self.set_pages(pages);
-    }
-    fn sidebar_w(&self) -> f32 {
-        self.sidebar_w()
-    }
-    fn set_sidebar_mode(&mut self, enabled: bool) {
-        self.set_sidebar_mode(enabled);
-    }
-    fn set_sidebar_label(&mut self, label: Option<String>) {
-        self.sidebar_label = label;
-        self.update_target_pos();
-    }
-    fn add_widget_to_page(&mut self, page_idx: usize, widget: *mut (dyn Widget + 'static)) {
-        self.add_widget_to_page(page_idx, widget);
-    }
-    fn clear_page_widgets(&mut self, page_idx: usize) {
-        self.clear_page_widgets(page_idx);
-    }
-    fn menu_items_list(&self) -> Vec<Vec<String>> {
-        self.sidebar_menu.menu_items_list()
-    }
-    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
-        self.sidebar_menu.menu_checked_list()
-    }
-}
-
-unsafe impl Send for Paginator {}
-unsafe impl Sync for Paginator {}
-
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
-pub struct Finger {
-    pub slot: usize,
-    pub x: f32,
-    pub y: f32,
-}
-
-#[derive(Debug, Clone)]
-pub struct Trackpad {
-    base: WidgetBase,
-    pub fingers: Vec<Finger>,
-}
-
-impl Trackpad {
-    pub fn new() -> Self {
-        Self {
-            base: WidgetBase::new(),
-            fingers: Vec::new(),
-        }
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn set_fingers(&mut self, fingers: Vec<Finger>) {
-        self.fingers = fingers;
-    }
-}
-
-impl Widget for Trackpad {
-    fn base(&self) -> Option<&WidgetBase> {
-        Some(&self.base)
-    }
-
-    fn base_mut(&mut self) -> Option<&mut WidgetBase> {
-        Some(&mut self.base)
-    }
-
-    fn color(&self) -> [f32; 4] {
-        [0.11, 0.11, 0.16, 0.85]
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let (x, y, w, h) = self.rect();
-        let mut quads = Vec::new();
-
-        // 1. Background
-        quads.push((x, y, w, h, [0.11, 0.11, 0.16, 0.85]));
-
-        // 2. Borders
-        let border_color = [0.28, 0.28, 0.38, 1.0];
-        quads.push((x, y, w, 1.0, border_color));             // Top
-        quads.push((x, y + h - 1.0, w, 1.0, border_color));     // Bottom
-        quads.push((x, y, 1.0, h, border_color));             // Left
-        quads.push((x + w - 1.0, y, 1.0, h, border_color));     // Right
-
-        // 3. Fingers
-        for finger in &self.fingers {
-            let rx = finger.x.clamp(0.0, 1.0);
-            let ry = finger.y.clamp(0.0, 1.0);
-            let fx = x + rx * w;
-            let fy = y + ry * h;
-            let dot_size = 12.0;
-
-            // Render glow (outer light blue rectangle)
-            quads.push((
-                fx - (dot_size + 6.0) / 2.0,
-                fy - (dot_size + 6.0) / 2.0,
-                dot_size + 6.0,
-                dot_size + 6.0,
-                [0.35, 0.55, 0.95, 0.4],
-            ));
-            // Render core (solid blue/purple rectangle)
-            quads.push((
-                fx - dot_size / 2.0,
-                fy - dot_size / 2.0,
-                dot_size,
-                dot_size,
-                [0.45, 0.65, 1.0, 1.0],
-            ));
-        }
-
-        quads
-    }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let (x, y, _w, h) = self.rect();
-        let mut labels = Vec::new();
-
-        // Render "Touchpad Area" label
-        labels.push(TextLabel {
-            text: "Touchpad Area".to_string(),
-            x: x + 12.0,
-            y: y + h - 22.0,
-            font_size: 11.0,
-            color: [0x73, 0x73, 0x8c],
-        });
-
-        // Optional widget-base label on top
-        if let Some(ref label) = self.base.label {
-            labels.push(TextLabel {
-                text: label.clone(),
-                x,
-                y: y - 18.0,
-                font_size: 12.0,
-                color: [0x83, 0x83, 0x8a],
-            });
-        }
-
-        labels
-    }
-
-    fn top_room(&self) -> f32 {
-        if self.base.label.is_some() {
-            16.0
-        } else {
-            0.0
-        }
-    }
-
-    fn draggable(&self) -> bool { true }
-    fn is_dragging(&self) -> bool { !self.fingers.is_empty() }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        let (x, y, w, h) = self.rect();
-        if w > 0.0 && h > 0.0 {
-            let rx = ((px - x) / w).clamp(0.0, 1.0);
-            let ry = ((py - y) / h).clamp(0.0, 1.0);
-            self.fingers = vec![Finger { slot: 0, x: rx, y: ry }];
-        }
-    }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        let (x, y, w, h) = self.rect();
-        if w > 0.0 && h > 0.0 {
-            let rx = ((px - x) / w).clamp(0.0, 1.0);
-            let ry = ((py - y) / h).clamp(0.0, 1.0);
-            self.fingers = vec![Finger { slot: 0, x: rx, y: ry }];
-            true
-        } else {
-            false
-        }
-    }
-
-    fn drag_end(&mut self) {
-        self.fingers.clear();
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button == MouseButton::Left {
-            let (x, y, w, h) = self.rect();
-            if px >= x && px <= x + w && py >= y && py <= y + h {
-                if state == ElementState::Pressed {
-                    let rx = ((px - x) / w).clamp(0.0, 1.0);
-                    let ry = ((py - y) / h).clamp(0.0, 1.0);
-                    self.fingers = vec![Finger { slot: 0, x: rx, y: ry }];
-                    return true;
-                } else {
-                    self.fingers.clear();
-                    return true;
-                }
-            }
-        }
-        false
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct Separator {
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-    pub color: [f32; 4],
-}
-
-impl Separator {
-    pub fn new(x: f32, y: f32, w: f32, h: f32, color: [f32; 4]) -> Self {
-        Self { x, y, w, h, color }
-    }
-}
-
-impl Widget for Separator {
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        (self.x, self.y, self.w, self.h)
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.x = x;
-        self.y = y;
-        self.w = w;
-        self.h = h;
-    }
-
-    fn color(&self) -> [f32; 4] {
-        self.color
-    }
-}
-
-fn serialize_single_widget(w: &dyn Widget, json: &mut String) {
-    let (x, y, width, height) = w.rect();
-    let label = w.label().or_else(|| w.base().and_then(|b| b.label.clone())).unwrap_or_default();
-    let focused = w.base().map_or(false, |b| b.focused);
-    let hovered = w.hovered();
-    let value = w.value();
-    let type_name = w.type_name();
-
-    // Escape JSON label
-    let escaped_label = label.replace('\\', "\\\\").replace('"', "\\\"");
-
-    json.push_str(&format!(
-        "{{\"type\":\"{}\",\"label\":\"{}\",\"rect\":[{},{},{},{}],\"focused\":{},\"hovered\":{},\"value\":{}",
-        type_name, escaped_label, x, y, width, height, focused, hovered, value
-    ));
-
-    // Handle children
-    let children = w.children();
-    let menu_items = w.menu_items();
-
-    if type_name == "Menu" && w.is_menu_open() && !menu_items.is_empty() {
-        json.push_str(",\"children\":[");
-        let mut max_len = 0;
-        for item in &menu_items {
-            max_len = max_len.max(item.len());
-        }
-        let dw = (max_len as f32 * 7.5 + 40.0).max(120.0);
-        let vertical = w.is_vertical();
-        let dx = if vertical { x + width } else { x };
-        let dy = if vertical { y } else { y + height };
-
-        let checked_states = w.menu_item_checked();
-        for (i, item) in menu_items.iter().enumerate() {
-            if i > 0 {
-                json.push(',');
-            }
-            let item_y = dy + i as f32 * DROPDOWN_ITEM_H;
-            let checked = checked_states.get(i).copied().flatten().unwrap_or(false);
-            let item_escaped = item.replace('\\', "\\\\").replace('"', "\\\"");
-            json.push_str(&format!(
-                "{{\"type\":\"MenuItem\",\"label\":\"{}\",\"rect\":[{},{},{},{}],\"focused\":false,\"hovered\":false,\"value\":{}}}",
-                item_escaped, dx, item_y, dw, DROPDOWN_ITEM_H, if checked { 1 } else { 0 }
-            ));
-        }
-        json.push_str("]}");
-    } else if !children.is_empty() {
-        json.push_str(",\"children\":[");
-        for (i, child_ptr) in children.iter().enumerate() {
-            if i > 0 {
-                json.push(',');
-            }
-            unsafe {
-                serialize_single_widget(&**child_ptr, json);
-            }
-        }
-        json.push_str("]}");
-    } else {
-        json.push('}');
-    }
-}
-
-pub fn serialize_widgets(widgets: &[Box<dyn Widget>]) -> String {
-    let mut json = String::new();
-    json.push('[');
-    for (i, w) in widgets.iter().enumerate() {
-        if i > 0 {
-            json.push(',');
-        }
-        serialize_single_widget(&**w, &mut json);
-    }
-    json.push(']');
-    json
-}
-
-pub struct Graph {
-    x: f32, y: f32, w: f32, h: f32,
-    hovered: bool,
-    show_network_grid: bool,
-    grid_size_x: f32,
-    grid_size_y: f32,
-    grid_origin_x: f32,
-    grid_origin_y: f32,
-    skipped_row_h: f32,
-    skipped_col_w: f32,
-    nodes: Vec<GraphNode>,
-    selected_idx: Option<usize>,
-    double_clicked_idx: Option<usize>,
-    double_click_timer: Option<(std::time::Instant, usize)>,
-    grid_snap_enabled: bool,
-    node_geom_toggled: Option<(usize, bool)>,
-
-    // For dragging a node
-    dragging_idx: Option<usize>,
-    drag_ox: f32,
-    drag_oy: f32,
-    drag_node_pos: Option<(f32, f32)>,
-
-    // Hover tracking
-    toggle_hovered_idx: Option<usize>,
-
-    uniform_background: bool,
-    network_opacity: f32,
-    cell_color: [f32; 3],
-    gap_color: [f32; 3],
-}
-
-impl Graph {
-    pub fn new() -> Self {
-        Self {
-            x: 0.0, y: 0.0, w: 0.0, h: 0.0,
-            hovered: false,
-            show_network_grid: false,
-            grid_size_x: 150.0,
-            grid_size_y: 75.0,
-            grid_origin_x: 0.0,
-            grid_origin_y: 0.0,
-            skipped_row_h: 37.5,
-            skipped_col_w: 37.5,
-            nodes: Vec::new(),
-            selected_idx: None,
-            double_clicked_idx: None,
-            double_click_timer: None,
-            grid_snap_enabled: false,
-            node_geom_toggled: None,
-            dragging_idx: None,
-            drag_ox: 0.0,
-            drag_oy: 0.0,
-            drag_node_pos: None,
-            toggle_hovered_idx: None,
-            uniform_background: false,
-            network_opacity: 0.95,
-            cell_color: [0.13, 0.13, 0.16],
-            gap_color: [0.07, 0.07, 0.09],
-        }
-    }
-
-    pub fn node_rect(&self, idx: usize) -> Option<(f32, f32, f32, f32)> {
-        let node = self.nodes.get(idx)?;
-        let (nx, ny) = if self.dragging_idx == Some(idx) {
-            self.drag_node_pos.unwrap_or((
-                node.position.0 * (self.grid_size_x + self.skipped_col_w) + self.grid_origin_x,
-                node.position.1 * (self.grid_size_y + self.skipped_row_h) + self.grid_origin_y,
-            ))
-        } else {
-            (
-                node.position.0 * (self.grid_size_x + self.skipped_col_w) + self.grid_origin_x,
-                node.position.1 * (self.grid_size_y + self.skipped_row_h) + self.grid_origin_y,
-            )
-        };
-        Some((nx, ny, self.grid_size_x, self.grid_size_y))
-    }
-
-    pub fn toggle_rect(&self, idx: usize) -> Option<(f32, f32, f32, f32)> {
-        let (nx, ny, nw, nh) = self.node_rect(idx)?;
-        Some((nx + nw - 30.0, ny + (nh - 18.0) / 2.0, 18.0, 18.0))
-    }
-
-    fn find_empty_cell(&self, start_x: f32, start_y: f32, skip_idx: Option<usize>) -> (f32, f32) {
-        let x = start_x;
-        let mut y = start_y;
-        loop {
-            let occupied = self.nodes.iter().enumerate().any(|(idx, node)| {
-                if Some(idx) == skip_idx {
-                    false
-                } else {
-                    (node.position.0 - x).abs() < 0.01 && (node.position.1 - y).abs() < 0.01
-                }
-            });
-            if occupied {
-                y += 1.0;
-            } else {
-                break;
-            }
-        }
-        (x, y)
-    }
-}
-
-impl Widget for Graph {
-    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
-    fn set_uniform_background(&mut self, uniform: bool) { self.uniform_background = uniform; }
-    fn set_network_opacity(&mut self, opacity: f32) { self.network_opacity = opacity; }
-    fn set_cell_color(&mut self, color: [f32; 3]) { self.cell_color = color; }
-    fn set_gap_color(&mut self, color: [f32; 3]) { self.gap_color = color; }
-    fn color(&self) -> [f32; 4] {
-        if self.uniform_background {
-            [0.10, 0.10, 0.13, self.network_opacity]
-        } else {
-            [0.0, 0.0, 0.0, 0.0]
-        }
-    }
-    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
-    fn hovered(&self) -> bool { self.hovered }
-    fn hit_test(&self, px: f32, py: f32) -> bool {
-        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        px >= self.x && px < self.x + self.w && py >= self.y && py < self.y + self.h
-    }
-
-    fn set_show_network_grid(&mut self, show: bool) { self.show_network_grid = show; }
-    fn set_grid_sizes(&mut self, gx: f32, gy: f32) { self.grid_size_x = gx; self.grid_size_y = gy; }
-    fn set_grid_origin(&mut self, ox: f32, oy: f32) { self.grid_origin_x = ox; self.grid_origin_y = oy; }
-    fn set_skipped_sizes(&mut self, row_h: f32, col_w: f32) { self.skipped_row_h = row_h; self.skipped_col_w = col_w; }
-
-    fn set_nodes(&mut self, nodes: &[GraphNode]) {
-        self.nodes = nodes.to_vec();
-        if let Some(sel) = self.selected_idx {
-            if sel >= self.nodes.len() {
-                self.selected_idx = None;
-            }
-        }
-    }
-    fn get_nodes(&self) -> Vec<GraphNode> { self.nodes.clone() }
-    fn selected_node(&self) -> Option<usize> { self.selected_idx }
-    fn set_selected_node(&mut self, idx: Option<usize>) { self.selected_idx = idx; }
-    fn double_clicked_node(&self) -> Option<usize> { self.double_clicked_idx }
-    fn clear_double_clicked_node(&mut self) { self.double_clicked_idx = None; }
-    fn set_grid_snap_enabled(&mut self, enabled: bool) { self.grid_snap_enabled = enabled; }
-    fn take_node_geom_toggle(&mut self) -> Option<(usize, bool)> { self.node_geom_toggled.take() }
-
-    fn text_labels(&self) -> Vec<TextLabel> {
-        let mut labels = Vec::new();
-        for (i, node) in self.nodes.iter().enumerate() {
-            if let Some((nx, ny, _nw, nh)) = self.node_rect(i) {
-                let lx = nx + 8.0;
-                let ly = ny + (nh - 12.0) / 2.0;
-                if lx >= self.x && lx < self.x + self.w && ly >= self.y && ly < self.y + self.h {
-                    labels.push(TextLabel {
-                        text: node.name.clone(),
-                        x: lx,
-                        y: ly,
-                        font_size: 14.0,
-                        color: [0xcc, 0xcc, 0xd4],
-                    });
-                }
-            }
-        }
-        labels
-    }
-
-    fn focus(&mut self) {
-        focus::set_focused(self);
-    }
-
-    fn is_dragging(&self) -> bool { self.dragging_idx.is_some() }
-    fn draggable(&self) -> bool { self.dragging_idx.is_some() }
-
-    fn drag_begin(&mut self, px: f32, py: f32) {
-        if let Some(idx) = self.dragging_idx {
-            if let Some((nx, ny, _, _)) = self.node_rect(idx) {
-                self.drag_ox = px - nx;
-                self.drag_oy = py - ny;
-                self.drag_node_pos = Some((nx, ny));
-            }
-        }
-    }
-
-    fn drag_update(&mut self, px: f32, py: f32) -> bool {
-        if self.dragging_idx.is_some() {
-            let nx = px - self.drag_ox;
-            let ny = py - self.drag_oy;
-
-            let snap_x = if self.grid_snap_enabled { self.grid_size_x + self.skipped_col_w } else { 0.0 };
-            let snap_y = if self.grid_snap_enabled { self.grid_size_y + self.skipped_row_h } else { 0.0 };
-
-            let nx = if snap_x > 0.0 {
-                let relative = nx - self.grid_origin_x;
-                let snapped = (relative / snap_x).round() * snap_x;
-                snapped + self.grid_origin_x
-            } else { nx };
-
-            let ny = if snap_y > 0.0 {
-                let relative = ny - self.grid_origin_y;
-                let snapped = (relative / snap_y).round() * snap_y;
-                snapped + self.grid_origin_y
-            } else { ny };
-
-            self.drag_node_pos = Some((nx, ny));
-            return true;
-        }
-        false
-    }
-
-    fn drag_end(&mut self) {
-        if let Some((nx, ny)) = self.drag_node_pos.take() {
-            let c = ((nx - self.grid_origin_x) / (self.grid_size_x + self.skipped_col_w)).round();
-            let r = ((ny - self.grid_origin_y) / (self.grid_size_y + self.skipped_row_h)).round();
-            if let Some(idx) = self.dragging_idx.take() {
-                let (nx, ny) = self.find_empty_cell(c, r, Some(idx));
-                self.nodes[idx].position = (nx, ny);
-            }
-        } else {
-            self.dragging_idx = None;
-        }
-    }
-
-    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
-        let was_toggle_hovered = self.toggle_hovered_idx;
-        self.toggle_hovered_idx = None;
-        for i in 0..self.nodes.len() {
-            if let Some((tx, ty, tw, th)) = self.toggle_rect(i) {
-                if px >= tx && px < tx + tw && py >= ty && py < ty + th {
-                    self.toggle_hovered_idx = Some(i);
-                    break;
-                }
-            }
-        }
-        was_toggle_hovered != self.toggle_hovered_idx
-    }
-
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
-        if button != MouseButton::Left { return false; }
-        match state {
-            ElementState::Pressed => {
-                for i in (0..self.nodes.len()).rev() {
-                    if let Some((tx, ty, tw, th)) = self.toggle_rect(i) {
-                        if px >= tx && px < tx + tw && py >= ty && py < ty + th {
-                            self.nodes[i].geom_visible = !self.nodes[i].geom_visible;
-                            self.node_geom_toggled = Some((i, self.nodes[i].geom_visible));
-                            return true;
-                        }
-                    }
-                    if let Some((nx, ny, nw, nh)) = self.node_rect(i) {
-                        if px >= nx && px < nx + nw && py >= ny && py < ny + nh {
-                            let now = std::time::Instant::now();
-                            if let Some((prev_time, prev_idx)) = self.double_click_timer {
-                                if prev_idx == i && now.duration_since(prev_time) < std::time::Duration::from_millis(500) {
-                                    self.double_clicked_idx = Some(i);
-                                }
-                            }
-                            self.double_click_timer = Some((now, i));
-                            self.selected_idx = Some(i);
-                            self.dragging_idx = Some(i);
-                            self.drag_ox = px - nx;
-                            self.drag_oy = py - ny;
-                            self.drag_node_pos = Some((nx, ny));
-                            self.focus();
-                            return true;
-                        }
-                    }
-                }
-                self.selected_idx = None;
-                false
-            }
-            ElementState::Released => {
-                if self.dragging_idx.is_some() {
-                    self.drag_end();
-                    return true;
-                }
-                false
-            }
-        }
-    }
-
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        let min_x = self.x;
-        let min_y = self.y;
-        let max_x = self.x + self.w;
-        let max_y = self.y + self.h;
-        let push_clipped = |qx: f32, qy: f32, qw: f32, qh: f32, qc: [f32; 4], q: &mut Vec<(f32, f32, f32, f32, [f32; 4])>| {
-            let rx1 = qx.max(min_x);
-            let ry1 = qy.max(min_y);
-            let rx2 = (qx + qw).min(max_x);
-            let ry2 = (qy + qh).min(max_y);
-            let rw = rx2 - rx1;
-            let rh = ry2 - ry1;
-            if rw > 0.0 && rh > 0.0 {
-                q.push((rx1, ry1, rw, rh, qc));
-            }
-        };
-
-        // Draw connection wires
-        let wire_color = [0.0, 0.75, 1.0, 0.7]; // Vibrant cyan glow
-        let wire_thickness = 3.0;
-        for i in 0..self.nodes.len() {
-            let node = &self.nodes[i];
-            if let Some((_, input_name, _)) = node.parameters.iter().find(|(name, _, _)| name.eq_ignore_ascii_case("input")) {
-                if let Some(src_idx) = self.nodes.iter().position(|n| n.name == *input_name) {
-                    if let (Some((sx, sy, sw, sh)), Some((ex, ey, ew, _eh))) = (self.node_rect(src_idx), self.node_rect(i)) {
-                        let start_x = sx + sw / 2.0;
-                        let start_y = sy + sh;
-                        let end_x = ex + ew / 2.0;
-                        let end_y = ey;
-
-                        let mid_y = start_y + (end_y - start_y) / 2.0;
-
-                        // Vertical segment 1
-                        let v1_min_y = start_y.min(mid_y);
-                        let v1_max_y = start_y.max(mid_y);
-                        push_clipped(
-                            start_x - wire_thickness / 2.0,
-                            v1_min_y,
-                            wire_thickness,
-                            v1_max_y - v1_min_y,
-                            wire_color,
-                            &mut quads,
-                        );
-
-                        // Horizontal segment
-                        let h_min_x = start_x.min(end_x);
-                        let h_max_x = start_x.max(end_x);
-                        push_clipped(
-                            h_min_x,
-                            mid_y - wire_thickness / 2.0,
-                            h_max_x - h_min_x,
-                            wire_thickness,
-                            wire_color,
-                            &mut quads,
-                        );
-
-                        // Vertical segment 2
-                        let v2_min_y = mid_y.min(end_y);
-                        let v2_max_y = mid_y.max(end_y);
-                        push_clipped(
-                            end_x - wire_thickness / 2.0,
-                            v2_min_y,
-                            wire_thickness,
-                            v2_max_y - v2_min_y,
-                            wire_color,
-                            &mut quads,
-                        );
-                    }
-                }
-            }
-        }
-
-        if self.show_network_grid && self.grid_size_x > 0.0 && self.grid_size_y > 0.0 && !self.uniform_background {
-            let step_y = self.grid_size_y + self.skipped_row_h;
-            let step_x = self.grid_size_x + self.skipped_col_w;
-
-            if step_y >= 4.0 && step_x >= 4.0 {
-                let ry_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
-                let ry_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
-                let ry_start = ry_start.max(-100_000);
-                let ry_end = ry_end.min(100_000);
-
-                let cx_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
-                let cx_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
-                let cx_start = cx_start.max(-100_000);
-                let cx_end = cx_end.min(100_000);
-
-                // Draw gap color as solid background color of grid
-                quads.push((self.x, self.y, self.w, self.h, [self.gap_color[0], self.gap_color[1], self.gap_color[2], self.network_opacity]));
-
-                // Draw filled cells with cell color
-                for r in ry_start..=ry_end {
-                    let y1 = self.grid_origin_y + (r as f32) * step_y;
-                    for c in cx_start..=cx_end {
-                        let x1 = self.grid_origin_x + (c as f32) * step_x;
-                        let cell_x = x1.max(self.x);
-                        let cell_y = y1.max(self.y);
-                        let cell_w = (x1 + self.grid_size_x).min(self.x + self.w) - cell_x;
-                        let cell_h = (y1 + self.grid_size_y).min(self.y + self.h) - cell_y;
-                        if cell_w > 0.0 && cell_h > 0.0 {
-                            quads.push((cell_x, cell_y, cell_w, cell_h, [self.cell_color[0], self.cell_color[1], self.cell_color[2], self.network_opacity]));
-                        }
-                    }
-                }
-
-                let grid_line_color = [0.18, 0.18, 0.22, 0.40];
-
-                for k in ry_start..=ry_end {
-                    let y1 = self.grid_origin_y + (k as f32) * step_y;
-                    let y2 = y1 + self.grid_size_y;
-                    if y1 < self.y + self.h {
-                        if y1 >= self.y {
-                            quads.push((self.x, y1, self.w, 1.0, grid_line_color));
-                        }
-                        if y2 >= self.y && y2 < self.y + self.h {
-                            quads.push((self.x, y2, self.w, 1.0, grid_line_color));
-                        }
-                    }
-                }
-
-                for k in cx_start..=cx_end {
-                    let x1 = self.grid_origin_x + (k as f32) * step_x;
-                    let x2 = x1 + self.grid_size_x;
-                    if x1 < self.x + self.w {
-                        if x1 >= self.x {
-                            quads.push((x1, self.y, 1.0, self.h, grid_line_color));
-                        }
-                        if x2 >= self.x && x2 < self.x + self.w {
-                            quads.push((x2, self.y, 1.0, self.h, grid_line_color));
-                        }
-                    }
-                }
-            }
-        }
-
-        for i in 0..self.nodes.len() {
-            if let Some((nx, ny, nw, nh)) = self.node_rect(i) {
-                let bg_color = if self.dragging_idx == Some(i) {
-                    colors::node_drag_color()
-                } else if self.selected_idx == Some(i) {
-                    colors::node_selected_color()
-                } else {
-                    colors::node_color()
-                };
-                push_clipped(nx, ny, nw, nh, bg_color, &mut quads);
-
-                if let Some((tx, ty, tw, th)) = self.toggle_rect(i) {
-                    let btn_color = if self.toggle_hovered_idx == Some(i) {
-                        colors::TOGGLE_HOVER
-                    } else {
-                        colors::TOGGLE_OFF
-                    };
-                    push_clipped(tx, ty, tw, th, btn_color, &mut quads);
-
-                    if self.nodes[i].geom_visible {
-                        let inset = 3.0;
-                        push_clipped(tx + inset, ty + inset, tw - inset * 2.0, th - inset * 2.0, colors::TOGGLE_ON, &mut quads);
-                    }
-                }
-            }
-        }
-
-        quads
-    }
-
-    fn grid_origin(&self) -> (f32, f32) {
-        (self.grid_origin_x, self.grid_origin_y)
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
-        if self.hit_test(px, py) {
-            match delta {
-                MouseScrollDelta::LineDelta(x, y) => {
-                    self.grid_origin_x += *x * 15.0;
-                    self.grid_origin_y += *y * 15.0;
-                    true
-                }
-                MouseScrollDelta::PixelDelta(pos) => {
-                    self.grid_origin_x += pos.x as f32;
-                    self.grid_origin_y += pos.y as f32;
-                    true
-                }
-            }
-        } else {
-            false
-        }
-    }
-}
-
-
-
-
diff --git a/src/widget/container.rs b/src/widget/container.rs
new file mode 100644
index 0000000..0daecb7
--- /dev/null
+++ b/src/widget/container.rs
@@ -0,0 +1,5076 @@
+use crate::colors;
+use crate::widget::*;
+use crate::widget::display::make_widget_text_buffer;
+use crate::widget::input::{BREADCRUMB_PADDING, SEGMENT_GAP};
+
+#[derive(Clone)]
+pub struct Container {
+    pub parent: Option<*mut (dyn Element + 'static)>,
+    pub children: Vec<*mut (dyn Element + 'static)>,
+}
+
+impl Container {
+    pub fn new() -> Self {
+        Self { parent: None, children: Vec::new() }
+    }
+}
+
+impl Element for Container {
+    fn rect(&self) -> (f32, f32, f32, f32) { (0.0, 0.0, 0.0, 0.0) }
+    fn set_rect(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
+    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
+
+    fn focus(&mut self) {
+        focus::set_focused(self);
+    }
+    fn unfocus(&mut self) {}
+
+    fn parent(&self) -> Option<*mut (dyn Element + 'static)> { self.parent }
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>) { self.parent = parent; }
+    fn children(&self) -> Vec<*mut (dyn Element + 'static)> { self.children.clone() }
+    fn add_child(&mut self, child: *mut (dyn Element + 'static)) { self.children.push(child); }
+    fn clear_children(&mut self) { self.children.clear(); }
+}
+
+impl Drop for Container {
+    fn drop(&mut self) {
+        focus::clear_if_matches(self);
+    }
+}
+
+
+pub struct Header {
+    x: f32, y: f32, w: f32, h: f32,
+    hovered: bool,
+}
+
+impl Header {
+    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
+}
+
+impl Element for Header {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn color(&self) -> [f32; 4] { colors::HEADER_BG }
+    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
+    fn hovered(&self) -> bool { self.hovered }
+}
+
+pub struct ContentBg {
+    x: f32, y: f32, w: f32, h: f32,
+    hovered: bool,
+    show_network_grid: bool,
+    grid_size_x: f32,
+    grid_size_y: f32,
+    grid_origin_x: f32,
+    grid_origin_y: f32,
+    skipped_row_h: f32,
+    skipped_col_w: f32,
+}
+
+impl ContentBg {
+    pub fn new() -> Self {
+        Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false, show_network_grid: false, grid_size_x: 150.0, grid_size_y: 75.0, grid_origin_x: 0.0, grid_origin_y: 0.0, skipped_row_h: 37.5, skipped_col_w: 37.5 }
+    }
+}
+
+impl Element for ContentBg {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn color(&self) -> [f32; 4] {
+        if self.show_network_grid {
+            [0.0, 0.0, 0.0, 0.0]
+        } else {
+            colors::CONTENT_BG
+        }
+    }
+    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
+    fn hovered(&self) -> bool { self.hovered }
+    fn hit_test(&self, _px: f32, _py: f32) -> bool { false }
+
+    fn set_show_network_grid(&mut self, show: bool) { self.show_network_grid = show; }
+    fn set_grid_sizes(&mut self, gx: f32, gy: f32) { self.grid_size_x = gx; self.grid_size_y = gy; }
+    fn set_grid_origin(&mut self, ox: f32, oy: f32) { self.grid_origin_x = ox; self.grid_origin_y = oy; }
+    fn set_skipped_sizes(&mut self, row_h: f32, col_w: f32) { self.skipped_row_h = row_h; self.skipped_col_w = col_w; }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        if !self.show_network_grid || self.grid_size_x <= 0.0 || self.grid_size_y <= 0.0 {
+            return vec![];
+        }
+        let mut quads = Vec::new();
+        let grid_color = [0.0, 0.0, 0.0, 0.0];
+        let max_alpha = colors::CONTENT_BG[3]; // Peak opacity in the middle of gradient cells matches non-gradient cells
+        let steps = 20; // Silky-smooth gradient transition
+
+        let step_y = self.grid_size_y + self.skipped_row_h;
+        let step_x = self.grid_size_x + self.skipped_col_w;
+
+        if step_y >= 4.0 && step_x >= 4.0 {
+            let ry_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
+            let ry_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
+            let ry_start = ry_start.max(-100_000);
+            let ry_end = ry_end.min(100_000);
+
+            let cx_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
+            let cx_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
+            let cx_start = cx_start.max(-100_000);
+            let cx_end = cx_end.min(100_000);
+
+            // Draw individual cell backgrounds to avoid stacking with gradients
+            for ry in ry_start..=ry_end {
+                let y1 = self.grid_origin_y + (ry as f32) * step_y;
+                let draw_start_y = y1.max(self.y);
+                let draw_end_y = (y1 + self.grid_size_y).min(self.y + self.h);
+                if draw_start_y < draw_end_y {
+                    for cx in cx_start..=cx_end {
+                        let x1 = self.grid_origin_x + (cx as f32) * step_x;
+                        let draw_start_x = x1.max(self.x);
+                        let draw_end_x = (x1 + self.grid_size_x).min(self.x + self.w);
+                        if draw_start_x < draw_end_x {
+                            quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, colors::CONTENT_BG));
+                        }
+                    }
+                }
+            }
+        }
+
+        // Draw interstitial row gradients (horizontal bands fading to 0 alpha at left and right sides)
+        if self.skipped_row_h > 0.0 {
+            let step_y = self.grid_size_y + self.skipped_row_h;
+            let step_x = self.grid_size_x + self.skipped_col_w;
+            if step_y >= 4.0 && step_x >= 4.0 {
+                let k_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
+                let k_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
+                let k_start = k_start.max(-100_000);
+                let k_end = k_end.min(100_000);
+
+                let cx_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
+                let cx_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
+                let cx_start = cx_start.max(-100_000);
+                let cx_end = cx_end.min(100_000);
+
+                for k in k_start..=k_end {
+                    let y1 = self.grid_origin_y + (k as f32) * step_y;
+                    let y2 = y1 + self.grid_size_y;
+                    if y1 >= self.y + self.h {
+                        continue;
+                    }
+                    let draw_start_y = y2.max(self.y);
+                    let draw_end_y = (y2 + self.skipped_row_h).min(self.y + self.h);
+                    if draw_start_y >= draw_end_y {
+                        continue;
+                    }
+
+                    for cx in cx_start..=cx_end {
+                        let x1 = self.grid_origin_x + (cx as f32) * step_x;
+                        let x_mid = x1 + self.grid_size_x / 2.0;
+                        let w_total = self.grid_size_x;
+                        let sub_w = w_total / steps as f32;
+
+                        for i in 0..steps {
+                            let sx_start = x1 + i as f32 * sub_w;
+                            let sx_end = sx_start + sub_w;
+                            let draw_start_x = sx_start.max(self.x);
+                            let draw_end_x = sx_end.min(self.x + self.w);
+                            if draw_start_x < draw_end_x {
+                                let sx_mid = (sx_start + sx_end) / 2.0;
+                                let dist = (sx_mid - x_mid).abs();
+                                let d = (dist / (w_total / 2.0)).min(1.0);
+                                
+                                // Fade the cell background color from max_alpha in the middle to transparent at the edges
+                                let alpha = max_alpha * (1.0 - d);
+                                if alpha > 0.001 {
+                                    quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Draw interstitial column gradients (vertical bands fading to 0 alpha at top and bottom)
+        if self.skipped_col_w > 0.0 {
+            let step_y = self.grid_size_y + self.skipped_row_h;
+            let step_x = self.grid_size_x + self.skipped_col_w;
+            if step_y >= 4.0 && step_x >= 4.0 {
+                let k_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
+                let k_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
+                let k_start = k_start.max(-100_000);
+                let k_end = k_end.min(100_000);
+
+                let ry_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
+                let ry_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
+                let ry_start = ry_start.max(-100_000);
+                let ry_end = ry_end.min(100_000);
+
+                for k in k_start..=k_end {
+                    let x1 = self.grid_origin_x + (k as f32) * step_x;
+                    let x2 = x1 + self.grid_size_x;
+                    if x1 >= self.x + self.w {
+                        continue;
+                    }
+                    let draw_start_x = x2.max(self.x);
+                    let draw_end_x = (x2 + self.skipped_col_w).min(self.x + self.w);
+                    if draw_start_x >= draw_end_x {
+                        continue;
+                    }
+
+                    for ry in ry_start..=ry_end {
+                        let y1 = self.grid_origin_y + (ry as f32) * step_y;
+                        let y_mid = y1 + self.grid_size_y / 2.0;
+                        let h_total = self.grid_size_y;
+                        let sub_h = h_total / steps as f32;
+
+                        for i in 0..steps {
+                            let sy_start = y1 + i as f32 * sub_h;
+                            let sy_end = sy_start + sub_h;
+                            let draw_start_y = sy_start.max(self.y);
+                            let draw_end_y = sy_end.min(self.y + self.h);
+                            if draw_start_y < draw_end_y {
+                                let sy_mid = (sy_start + sy_end) / 2.0;
+                                let dist = (sy_mid - y_mid).abs();
+                                let d = (dist / (h_total / 2.0)).min(1.0);
+                                
+                                // Fade the cell background color from max_alpha in the middle to transparent at the edges
+                                let alpha = max_alpha * (1.0 - d);
+                                if alpha > 0.001 {
+                                    quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Draw the grid borders
+        let step_y = self.grid_size_y + self.skipped_row_h;
+        if step_y >= 4.0 {
+            let k_start = ((self.y - self.grid_origin_y) / step_y).floor() as i32 - 1;
+            let k_end = ((self.y + self.h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
+            let k_start = k_start.max(-100_000);
+            let k_end = k_end.min(100_000);
+            for k in k_start..=k_end {
+                let y1 = self.grid_origin_y + (k as f32) * step_y;
+                let y2 = y1 + self.grid_size_y;
+                if y1 >= self.y + self.h {
+                    continue;
+                }
+                if y1 >= self.y {
+                    quads.push((self.x, y1, self.w, 1.0, grid_color));
+                }
+                if y2 >= self.y && y2 < self.y + self.h {
+                    quads.push((self.x, y2, self.w, 1.0, grid_color));
+                }
+            }
+        }
+
+        let step_x = self.grid_size_x + self.skipped_col_w;
+        if step_x >= 4.0 {
+            let k_start = ((self.x - self.grid_origin_x) / step_x).floor() as i32 - 1;
+            let k_end = ((self.x + self.w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
+            let k_start = k_start.max(-100_000);
+            let k_end = k_end.min(100_000);
+            for k in k_start..=k_end {
+                let x1 = self.grid_origin_x + (k as f32) * step_x;
+                let x2 = x1 + self.grid_size_x;
+                if x1 >= self.x + self.w {
+                    continue;
+                }
+                if x1 >= self.x {
+                    quads.push((x1, self.y, 1.0, self.h, grid_color));
+                }
+                if x2 >= self.x && x2 < self.x + self.w {
+                    quads.push((x2, self.y, 1.0, self.h, grid_color));
+                }
+            }
+        }
+        quads
+    }
+}
+
+pub struct ViewportBg {
+    x: f32, y: f32, w: f32, h: f32,
+    hovered: bool,
+}
+
+impl ViewportBg {
+    pub fn new() -> Self { Self { x: 0.0, y: 0.0, w: 0.0, h: 0.0, hovered: false } }
+}
+
+impl Element for ViewportBg {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+    fn color(&self) -> [f32; 4] { colors::VIEWPORT_BG }
+    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
+    fn hovered(&self) -> bool { self.hovered }
+    fn hit_test(&self, _px: f32, _py: f32) -> bool { false }
+}
+
+pub struct ParametersBg {
+    x: f32, y: f32, w: f32, h: f32,
+    hovered: bool,
+    display_params: Vec<(String, String, String)>,
+    dragging_param: Option<usize>,
+    pub focused_param: Option<usize>,
+    mouse_pos: Option<(f32, f32)>,
+    sliders: Vec<Option<Slider>>,
+    float3s: Vec<Option<Float3>>,
+    spinboxes: Vec<Option<Spinbox>>,
+    visible: bool,
+}
+
+impl ParametersBg {
+    pub fn new() -> Self {
+        Self {
+            x: 0.0,
+            y: 0.0,
+            w: 0.0,
+            h: 0.0,
+            hovered: false,
+            display_params: Vec::new(),
+            dragging_param: None,
+            focused_param: None,
+            mouse_pos: None,
+            sliders: Vec::new(),
+            float3s: Vec::new(),
+            spinboxes: Vec::new(),
+            visible: true,
+        }
+    }
+
+    pub fn get_param_rects(&self) -> Vec<(f32, f32, f32, f32)> {
+        let mut rects = Vec::new();
+        let mut cur_y = self.y + 30.0;
+        for p in &self.display_params {
+            let h = if p.2 == "code" {
+                200.0
+            } else if p.2 == "section" {
+                24.0
+            } else if p.2.starts_with("float3") {
+                108.0
+            } else if p.2 == "text" {
+                24.0
+            } else {
+                20.0
+            };
+            rects.push((self.x + 8.0, cur_y, self.w - 16.0, h));
+            cur_y += h + 8.0;
+        }
+        rects
+    }
+
+    fn update_slider_rects(&mut self) {
+        let rects = self.get_param_rects();
+        for (i, s_opt) in self.sliders.iter_mut().enumerate() {
+            if let Some(s) = s_opt {
+                let r = rects[i];
+                let track_x = self.x + 100.0;
+                let track_w = (self.w - 100.0 - 20.0).max(10.0);
+                let track_y = r.1 + 4.0;
+                let track_h = 12.0;
+                s.set_rect(track_x, track_y, track_w, track_h);
+            }
+        }
+        for (i, f_opt) in self.float3s.iter_mut().enumerate() {
+            if let Some(f) = f_opt {
+                let r = rects[i];
+                f.set_rect(r.0, r.1, r.2, r.3);
+            }
+        }
+        for (i, sb_opt) in self.spinboxes.iter_mut().enumerate() {
+            if let Some(sb) = sb_opt {
+                let r = rects[i];
+                let box_x = self.x + 100.0;
+                let box_w = (self.w - 100.0 - 16.0).max(10.0);
+                sb.set_rect(box_x, r.1, box_w, r.3);
+            }
+        }
+    }
+}
+
+fn parse_slider_range(ptype: &str) -> (f32, f32) {
+    if ptype.starts_with("slider:") || ptype.starts_with("float3:") {
+        let parts: Vec<&str> = ptype.split(':').collect();
+        if parts.len() >= 3 {
+            if let (Ok(min), Ok(max)) = (parts[1].parse::<f32>(), parts[2].parse::<f32>()) {
+                return (min, max);
+            }
+        }
+    }
+    (0.0, 2.0)
+}
+
+fn parse_spinbox_range(ptype: &str) -> (i32, i32, i32) {
+    if ptype.starts_with("spinbox:") {
+        let parts: Vec<&str> = ptype.split(':').collect();
+        if parts.len() >= 4 {
+            if let (Ok(min), Ok(max), Ok(step)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>(), parts[3].parse::<i32>()) {
+                return (min, max, step);
+            }
+        } else if parts.len() == 3 {
+            if let (Ok(min), Ok(max)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>()) {
+                return (min, max, 1);
+            }
+        }
+    }
+    (0, 10000, 1)
+}
+
+fn parse_float3_value(val_str: &str, min: f32, max: f32) -> [f32; 3] {
+    let mut out = [0.5, 0.5, 0.5];
+    let parts: Vec<&str> = val_str
+        .split(|c| c == ':' || c == ',' || c == ' ')
+        .filter(|s| !s.is_empty())
+        .collect();
+    for i in 0..3 {
+        if i < parts.len() {
+            if let Ok(v) = parts[i].parse::<f32>() {
+                let range = max - min;
+                if range != 0.0 {
+                    out[i] = ((v - min) / range).clamp(0.0, 1.0);
+                } else {
+                    out[i] = 0.0;
+                }
+            }
+        }
+    }
+    out
+}
+
+impl Element for ParametersBg {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.x = x; self.y = y; self.w = w; self.h = h;
+        self.update_slider_rects();
+    }
+    fn color(&self) -> [f32; 4] {
+        if !self.visible {
+            return [0.0, 0.0, 0.0, 0.0];
+        }
+        colors::PARAM_BG
+    }
+    fn set_hovered(&mut self, v: bool) { self.hovered = v; }
+    fn hovered(&self) -> bool { self.hovered }
+    fn set_visible(&mut self, visible: bool) {
+        self.visible = visible;
+    }
+    fn visible(&self) -> bool {
+        self.visible
+    }
+    fn hit_test(&self, px: f32, py: f32) -> bool {
+        if !self.visible {
+            return false;
+        }
+        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+            return false;
+        }
+        px >= self.x && px <= self.x + self.w && py >= self.y && py <= self.y + self.h
+    }
+
+    fn set_display_params(&mut self, params: &[(String, String, String)]) {
+        let mut layout_changed = self.display_params.len() != params.len();
+        if !layout_changed {
+            for (p_old, p_new) in self.display_params.iter().zip(params.iter()) {
+                if p_old.0 != p_new.0 || p_old.2 != p_new.2 {
+                    layout_changed = true;
+                    break;
+                }
+            }
+        }
+
+        if layout_changed {
+            self.display_params = params.to_vec();
+            self.focused_param = None;
+            self.sliders = self.display_params.iter().map(|p| {
+                if p.2.starts_with("slider") {
+                    let val = p.1.parse::<f32>().unwrap_or(0.0);
+                    let (min, max) = parse_slider_range(&p.2);
+                    let t = if max - min != 0.0 {
+                        ((val - min) / (max - min)).clamp(0.0, 1.0)
+                    } else {
+                        0.0
+                    };
+                    Some(Slider::new().with_value(t).with_range(min, max).with_readout(true))
+                } else {
+                    None
+                }
+            }).collect();
+            self.float3s = self.display_params.iter().map(|p| {
+                if p.2.starts_with("float3") {
+                    let (min, max) = parse_slider_range(&p.2);
+                    let vals = parse_float3_value(&p.1, min, max);
+                    Some(Float3::new().with_values(vals).with_range(min, max).with_label(&p.0))
+                } else {
+                    None
+                }
+            }).collect();
+            self.spinboxes = self.display_params.iter().map(|p| {
+                if p.2.starts_with("spinbox") {
+                    let (min, max, step) = parse_spinbox_range(&p.2);
+                    let val = p.1.parse::<i32>().unwrap_or(min);
+                    Some(Spinbox::new(val, min, max, step))
+                } else {
+                    None
+                }
+            }).collect();
+        } else {
+            for (i, p_new) in params.iter().enumerate() {
+                if Some(i) != self.focused_param && Some(i) != self.dragging_param {
+                    self.display_params[i].1 = p_new.1.clone();
+                    if let Some(ref mut s) = self.sliders[i] {
+                        let val = p_new.1.parse::<f32>().unwrap_or(0.0);
+                        let (min, max) = parse_slider_range(&p_new.2);
+                        let t = if max - min != 0.0 {
+                            ((val - min) / (max - min)).clamp(0.0, 1.0)
+                        } else {
+                            0.0
+                        };
+                        s.set_value(t);
+                    } else if let Some(ref mut f) = self.float3s[i] {
+                        let (min, max) = parse_slider_range(&p_new.2);
+                        let vals = parse_float3_value(&p_new.1, min, max);
+                        f.set_values(vals);
+                    } else if let Some(ref mut sb) = self.spinboxes[i] {
+                        if !sb.editing {
+                            let (min, _max, _step) = parse_spinbox_range(&p_new.2);
+                            let val = p_new.1.parse::<i32>().unwrap_or(min);
+                            sb.value = val;
+                        }
+                    }
+                }
+            }
+        }
+        self.update_slider_rects();
+    }
+
+    fn unfocus(&mut self) {
+        if let Some(idx) = self.focused_param {
+            if idx < self.display_params.len() {
+                let p = &mut self.display_params[idx];
+                if p.2.starts_with("spinbox") {
+                    if let Some(sb) = &mut self.spinboxes[idx] {
+                        sb.unfocus();
+                        p.1 = sb.value.to_string();
+                    }
+                } else if p.2.starts_with("slider") {
+                    if let Some(s) = &mut self.sliders[idx] {
+                        s.unfocus();
+                        let (min, max) = parse_slider_range(&p.2);
+                        let new_val = min + s.value * (max - min);
+                        p.1 = format!("{:.2}", new_val);
+                    }
+                } else if p.2.starts_with("float3") {
+                    if let Some(f) = &mut self.float3s[idx] {
+                        f.unfocus();
+                        let (min, max) = parse_slider_range(&p.2);
+                        let val0 = min + f.values[0] * (max - min);
+                        let val1 = min + f.values[1] * (max - min);
+                        let val2 = min + f.values[2] * (max - min);
+                        p.1 = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
+                    }
+                }
+            }
+        }
+        self.focused_param = None;
+    }
+
+    fn node_params(&self) -> Vec<(String, String, String)> {
+        self.display_params.clone()
+    }
+
+    fn draggable(&self) -> bool {
+        self.dragging_param.is_some() 
+            || self.display_params.iter().any(|p| p.2.starts_with("slider") || p.2.starts_with("float3"))
+    }
+
+    fn is_dragging(&self) -> bool {
+        self.dragging_param.is_some()
+    }
+
+    fn drag_begin(&mut self, px: f32, py: f32) {
+        let rects = self.get_param_rects();
+        for (i, p) in self.display_params.iter().enumerate() {
+            if p.2.starts_with("slider") {
+                let r = rects[i];
+                let row_y = r.1;
+                if py >= row_y - 2.0 && py <= row_y + 18.0 {
+                    if let Some(s) = &mut self.sliders[i] {
+                        s.drag_begin(px, py);
+                        self.dragging_param = Some(i);
+                        break;
+                    }
+                }
+            } else if p.2.starts_with("float3") {
+                let r = rects[i];
+                if py >= r.1 && py <= r.1 + r.3 {
+                    if let Some(f) = &mut self.float3s[i] {
+                        if f.mouse_input(MouseButton::Left, ElementState::Pressed, px, py) {
+                            self.dragging_param = Some(i);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn drag_update(&mut self, px: f32, py: f32) -> bool {
+        if let Some(i) = self.dragging_param {
+            if let Some(s) = &mut self.sliders[i] {
+                if s.drag_update(px, py) {
+                    let (min, max) = parse_slider_range(&self.display_params[i].2);
+                    let new_val = min + s.value * (max - min);
+                    let old_val = &self.display_params[i].1;
+                    let new_val_str = format!("{:.2}", new_val);
+                    if *old_val != new_val_str {
+                        self.display_params[i].1 = new_val_str;
+                        return true;
+                    }
+                }
+            } else if let Some(f) = &mut self.float3s[i] {
+                if f.drag_update(px, py) {
+                    let (min, max) = parse_slider_range(&self.display_params[i].2);
+                    let val0 = min + f.values[0] * (max - min);
+                    let val1 = min + f.values[1] * (max - min);
+                    let val2 = min + f.values[2] * (max - min);
+                    let new_val_str = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
+                    let old_val = &self.display_params[i].1;
+                    if *old_val != new_val_str {
+                        self.display_params[i].1 = new_val_str;
+                        return true;
+                    }
+                }
+            }
+        }
+        false
+    }
+
+    fn drag_end(&mut self) {
+        if let Some(i) = self.dragging_param.take() {
+            if let Some(s) = &mut self.sliders[i] {
+                s.drag_end();
+            } else if let Some(f) = &mut self.float3s[i] {
+                f.drag_end();
+            }
+        }
+    }
+
+    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        self.mouse_pos = Some((px, py));
+        true
+    }
+
+    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
+        if button == MouseButton::Left && state == ElementState::Pressed {
+            let rects = self.get_param_rects();
+            let mut clicked_any_focusable = false;
+            for (i, p) in self.display_params.iter_mut().enumerate() {
+                if p.2 == "code" {
+                    let r = rects[i];
+                    if px >= r.0 && px <= r.0 + r.2 && py >= r.1 + 18.0 && py <= r.1 + r.3 {
+                        self.focused_param = Some(i);
+                        clicked_any_focusable = true;
+                        break;
+                    }
+                } else if p.2 == "text" {
+                    let box_x = self.x + 100.0;
+                    let box_w = (self.w - 100.0 - 16.0).max(10.0);
+                    let r = rects[i];
+                    if px >= box_x && px <= box_x + box_w && py >= r.1 && py <= r.1 + r.3 {
+                        self.focused_param = Some(i);
+                        clicked_any_focusable = true;
+                        break;
+                    }
+                } else if p.2.starts_with("spinbox") {
+                    if let Some(sb) = &mut self.spinboxes[i] {
+                        if sb.mouse_input(button, state, px, py) {
+                            p.1 = sb.value.to_string();
+                            if sb.editing {
+                                self.focused_param = Some(i);
+                            } else {
+                                self.unfocus();
+                            }
+                            return true;
+                        }
+                    }
+                } else if p.2.starts_with("slider") {
+                    let r = rects[i];
+                    if py >= r.1 && py <= r.1 + r.3 {
+                        if let Some(s) = &mut self.sliders[i] {
+                            if s.mouse_input(button, state, px, py) {
+                                if s.editing {
+                                    self.focused_param = Some(i);
+                                    clicked_any_focusable = true;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } else if p.2.starts_with("float3") {
+                    let r = rects[i];
+                    if py >= r.1 && py <= r.1 + r.3 {
+                        if let Some(f) = &mut self.float3s[i] {
+                            if f.mouse_input(button, state, px, py) {
+                                if f.editing_idx.is_some() {
+                                    self.focused_param = Some(i);
+                                    clicked_any_focusable = true;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            if !clicked_any_focusable {
+                self.unfocus();
+            }
+            return true;
+        }
+        false
+    }
+
+    fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
+        if let Some(idx) = self.focused_param {
+            if event.state == ElementState::Pressed {
+                let p = &mut self.display_params[idx];
+                if p.2 == "code" {
+                    match &event.logical_key {
+                        Key::Named(NamedKey::Backspace) => {
+                            if !p.1.is_empty() {
+                                p.1.pop();
+                                return true;
+                            }
+                        }
+                        Key::Named(NamedKey::Enter) => {
+                            p.1.push('\n');
+                            return true;
+                        }
+                        Key::Named(NamedKey::Escape) => {
+                            self.focused_param = None;
+                            return true;
+                        }
+                        Key::Character(s) => {
+                            p.1.push_str(s);
+                            return true;
+                        }
+                        _ => {}
+                    }
+                } else if p.2 == "text" {
+                    match &event.logical_key {
+                        Key::Named(NamedKey::Backspace) => {
+                            if !p.1.is_empty() {
+                                p.1.pop();
+                                return true;
+                            }
+                        }
+                        Key::Named(NamedKey::Enter) | Key::Named(NamedKey::Escape) => {
+                            self.focused_param = None;
+                            return true;
+                        }
+                        Key::Character(s) => {
+                            p.1.push_str(s);
+                            return true;
+                        }
+                        _ => {}
+                    }
+                } else if p.2.starts_with("spinbox") {
+                    if let Some(sb) = &mut self.spinboxes[idx] {
+                        if sb.keyboard_input(event) {
+                            if !sb.editing {
+                                p.1 = sb.value.to_string();
+                                self.focused_param = None;
+                            } else {
+                                p.1 = sb.edit_buffer.clone();
+                            }
+                            return true;
+                        }
+                    }
+                } else if p.2.starts_with("slider") {
+                    if let Some(s) = &mut self.sliders[idx] {
+                        if s.keyboard_input(event) {
+                            let (min, max) = parse_slider_range(&p.2);
+                            let new_val = min + s.value * (max - min);
+                            p.1 = format!("{:.2}", new_val);
+                            if !s.editing {
+                                self.focused_param = None;
+                            }
+                            return true;
+                        }
+                    }
+                } else if p.2.starts_with("float3") {
+                    if let Some(f) = &mut self.float3s[idx] {
+                        if f.keyboard_input(event) {
+                            let (min, max) = parse_slider_range(&p.2);
+                            let val0 = min + f.values[0] * (max - min);
+                            let val1 = min + f.values[1] * (max - min);
+                            let val2 = min + f.values[2] * (max - min);
+                            p.1 = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
+                            if f.editing_idx.is_none() {
+                                self.focused_param = None;
+                            }
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        false
+    }
+
+    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32) -> bool {
+        let mut changed = false;
+        let rects = self.get_param_rects();
+        for (i, p) in self.display_params.iter_mut().enumerate() {
+            if p.2.starts_with("slider") {
+                let r = rects[i];
+                let row_y = r.1;
+                if py >= row_y - 2.0 && py <= row_y + 18.0 && px >= self.x && px <= self.x + self.w {
+                    if let Some(s) = &mut self.sliders[i] {
+                        let was_scroll = s.scroll_enabled;
+                        s.set_scroll(true);
+                        if s.mouse_wheel(delta, px, py) {
+                            let (min, max) = parse_slider_range(&p.2);
+                            let new_val = min + s.value * (max - min);
+                            let old_val = &p.1;
+                            let new_val_str = format!("{:.2}", new_val);
+                            if *old_val != new_val_str {
+                                p.1 = new_val_str;
+                                changed = true;
+                            }
+                        }
+                        s.set_scroll(was_scroll);
+                    }
+                }
+            } else if p.2.starts_with("float3") {
+                let r = rects[i];
+                let row_y = r.1;
+                if py >= row_y && py <= row_y + r.3 && px >= self.x && px <= self.x + self.w {
+                    if let Some(f) = &mut self.float3s[i] {
+                        let rects_inner = f.get_row_rects();
+                        for j in 0..3 {
+                            let r_inner = rects_inner[j];
+                            if py >= r_inner.1 && py <= r_inner.1 + r_inner.3 {
+                                let scroll_amount = match delta {
+                                    MouseScrollDelta::LineDelta(_x, y) => *y,
+                                    MouseScrollDelta::PixelDelta(pos) => (pos.y as f32) / 120.0,
+                                };
+                                let step = 0.02;
+                                let new_val = (f.values[j] - scroll_amount * step).clamp(0.0, 1.0);
+                                if (new_val - f.values[j]).abs() > 0.0001 {
+                                    f.values[j] = new_val;
+                                    if f.editing_idx == Some(j) {
+                                        let scaled_val = f.mins[j] + f.values[j] * (f.maxs[j] - f.mins[j]);
+                                        f.edit_buffer = format!("{:.2}", scaled_val);
+                                    }
+                                    let (min, max) = parse_slider_range(&p.2);
+                                    let val0 = min + f.values[0] * (max - min);
+                                    let val1 = min + f.values[1] * (max - min);
+                                    let val2 = min + f.values[2] * (max - min);
+                                    let new_val_str = format!("{:.2}:{:.2}:{:.2}", val0, val1, val2);
+                                    if p.1 != new_val_str {
+                                        p.1 = new_val_str;
+                                        changed = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } else if p.2.starts_with("spinbox") {
+                let r = rects[i];
+                let row_y = r.1;
+                if py >= row_y && py <= row_y + r.3 && px >= self.x && px <= self.x + self.w {
+                    if let Some(sb) = &mut self.spinboxes[i] {
+                        let scroll_amount = match delta {
+                            MouseScrollDelta::LineDelta(_x, y) => *y as i32,
+                            MouseScrollDelta::PixelDelta(pos) => {
+                                let dy = pos.y;
+                                if dy > 0.0 { 1 } else if dy < 0.0 { -1 } else { 0 }
+                            }
+                        };
+                        let new_val = (sb.value + scroll_amount * sb.step).clamp(sb.min, sb.max);
+                        if sb.value != new_val {
+                            sb.value = new_val;
+                            p.1 = new_val.to_string();
+                            changed = true;
+                        }
+                    }
+                }
+            }
+        }
+        changed
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut quads = Vec::new();
+        let rects = self.get_param_rects();
+
+        // Find sections and their ranges
+        let mut sections = Vec::new();
+        let mut current_section: Option<(usize, usize)> = None;
+        let mut in_section = false;
+        for (i, p) in self.display_params.iter().enumerate() {
+            if p.2 == "section" {
+                if let Some((start, end)) = current_section {
+                    sections.push((start, end));
+                }
+                current_section = None;
+                in_section = true;
+            } else {
+                if in_section {
+                    if let Some((_, ref mut end)) = current_section {
+                        *end = i;
+                    } else {
+                        current_section = Some((i, i));
+                    }
+                }
+            }
+        }
+        if let Some((start, end)) = current_section {
+            sections.push((start, end));
+        }
+
+        // Draw section border boxes
+        for (start, end) in sections {
+            if start <= end && start < rects.len() && end < rects.len() {
+                let r_start = rects[start];
+                let r_end = rects[end];
+                let bx = self.x + 4.0;
+                let bw = self.w - 8.0;
+                let by = r_start.1 - 4.0;
+                let bh = (r_end.1 + r_end.3 + 4.0) - by;
+                
+                let border_color = [0.18, 0.18, 0.27, 1.0];
+                let border_t = 1.0;
+                
+                // Top border
+                quads.push((bx, by, bw, border_t, border_color));
+                // Bottom border
+                quads.push((bx, by + bh - border_t, bw, border_t, border_color));
+                // Left border
+                quads.push((bx, by, border_t, bh, border_color));
+                // Right border
+                quads.push((bx + bw - border_t, by, border_t, bh, border_color));
+            }
+        }
+
+        for (i, p) in self.display_params.iter().enumerate() {
+            let r = rects[i];
+            if p.2.starts_with("slider") {
+                if let Some(s) = &self.sliders[i] {
+                    let (sx, sy, sw, sh) = s.rect();
+                    quads.push((sx, sy, sw, sh, s.color()));
+                    quads.extend(s.extra_quads());
+                }
+            } else if p.2 == "section" {
+                // Section header line is handled by the border box top border now
+            } else if p.2.starts_with("float3") {
+                if let Some(f) = &self.float3s[i] {
+                    quads.extend(f.extra_quads());
+                }
+            } else if p.2 == "code" {
+                quads.push((r.0, r.1 + 18.0, r.2, r.3 - 18.0, [0.08, 0.08, 0.10, 1.0]));
+                let border_color = if self.focused_param == Some(i) {
+                    [0.25, 0.45, 0.85, 1.0]
+                } else {
+                    [0.20, 0.20, 0.25, 1.0]
+                };
+                let (bx, by, bw, bh) = (r.0, r.1 + 18.0, r.2, r.3 - 18.0);
+                quads.push((bx, by, bw, 1.0, border_color));
+                quads.push((bx, by + bh - 1.0, bw, 1.0, border_color));
+                quads.push((bx, by, 1.0, bh, border_color));
+                quads.push((bx + bw - 1.0, by, 1.0, bh, border_color));
+            } else if p.2 == "text" {
+                let box_x = self.x + 100.0;
+                let box_w = (self.w - 100.0 - 16.0).max(10.0);
+                quads.push((box_x, r.1, box_w, r.3, [0.08, 0.08, 0.10, 1.0]));
+                let border_color = if self.focused_param == Some(i) {
+                    [0.25, 0.45, 0.85, 1.0]
+                } else {
+                    [0.20, 0.20, 0.25, 1.0]
+                };
+                let (bx, by, bw, bh) = (box_x, r.1, box_w, r.3);
+                let border_t = 1.0;
+                quads.push((bx, by, bw, border_t, border_color));
+                quads.push((bx, by + bh - border_t, bw, border_t, border_color));
+                quads.push((bx, by, border_t, bh, border_color));
+                quads.push((bx + bw - border_t, by, border_t, bh, border_color));
+            } else if p.2.starts_with("spinbox") {
+                if let Some(sb) = &self.spinboxes[i] {
+                    quads.push((sb.base.x, sb.base.y, sb.base.w, sb.base.h, sb.color()));
+                    quads.extend(sb.extra_quads());
+                }
+            }
+        }
+        quads
+    }
+
+    fn text_labels(&self) -> Vec<TextLabel> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let rects = self.get_param_rects();
+        let mut labels = Vec::new();
+        for (i, (name, value, ptype)) in self.display_params.iter().enumerate() {
+            let r = rects[i];
+            if ptype.starts_with("slider") {
+                labels.push(TextLabel {
+                    text: name.clone(),
+                    x: self.x + 8.0,
+                    y: r.1,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+                if let Some(s) = &self.sliders[i] {
+                    labels.extend(s.text_labels());
+                }
+            } else if ptype.starts_with("float3") {
+                if let Some(f) = &self.float3s[i] {
+                    labels.extend(f.text_labels());
+                }
+            } else if ptype == "section" {
+                labels.push(TextLabel {
+                    text: name.clone(),
+                    x: self.x + 12.0,
+                    y: r.1 + 2.0,
+                    font_size: 13.0,
+                    color: [0xee, 0xee, 0xf0],
+                });
+            } else if ptype == "code" {
+                labels.push(TextLabel {
+                    text: format!("{}:\n{}", name, value),
+                    x: self.x + 12.0,
+                    y: r.1,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+            } else if ptype.starts_with("spinbox") {
+                labels.push(TextLabel {
+                    text: name.clone(),
+                    x: self.x + 8.0,
+                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+                if let Some(sb) = &self.spinboxes[i] {
+                    labels.extend(sb.text_labels());
+                }
+            } else if ptype == "text" {
+                labels.push(TextLabel {
+                    text: name.clone(),
+                    x: self.x + 8.0,
+                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+                let val_text = if self.focused_param == Some(i) {
+                    format!("{}|", value)
+                } else {
+                    value.clone()
+                };
+                labels.push(TextLabel {
+                    text: val_text,
+                    x: self.x + 106.0,
+                    y: r.1 + (r.3 - 12.0) / 2.0 - 2.0,
+                    font_size: 12.0,
+                    color: [0xee, 0xee, 0xf0],
+                });
+            } else {
+                labels.push(TextLabel {
+                    text: format!("{}: {}", name, value),
+                    x: self.x + 8.0,
+                    y: r.1,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+            }
+        }
+        labels
+    }
+}
+
+pub struct MenuBar {
+    x: f32, y: f32, w: f32, h: f32,
+    hovering: bool,
+    pub title: String,
+    pub menus: Vec<Box<Menu>>,
+    pub menu_items: Vec<String>,
+    pub vertical_items: Vec<String>,
+    pub menu_dropdowns: Vec<Vec<String>>,
+    pub menu_dropdown_checked: Vec<Vec<Option<bool>>>,
+    pub hovered_menu: Option<usize>,
+    pub open_menu: Option<usize>,
+    pub hovered_dropdown: Option<usize>,
+    pub clicked_dropdown: Option<(usize, usize)>,
+    pub was_open: Option<usize>,
+    pub vertical: bool,
+    pub visible: bool,
+    pub focused: bool,
+    pub z_level: i32,
+    pub center_items: bool,
+    pub curved_circle: Option<(f32, f32, f32)>,
+    pub title_pos: Option<(f32, f32)>,
+    pub title_buf: Option<glyphon::Buffer>,
+    pub curved_title_char_bufs: Vec<glyphon::Buffer>,
+    pub network_opacity: f32,
+}
+
+impl MenuBar {
+    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+        Self {
+            x, y, w, h, hovering: false,
+            title: String::new(),
+            menus: Vec::new(),
+            menu_items: Vec::new(),
+            vertical_items: Vec::new(),
+            menu_dropdowns: Vec::new(),
+            menu_dropdown_checked: Vec::new(),
+            hovered_menu: None,
+            open_menu: None,
+            hovered_dropdown: None,
+            clicked_dropdown: None,
+            was_open: None,
+            vertical: false,
+            visible: true,
+            focused: false,
+            z_level: 100,
+            center_items: false,
+            curved_circle: None,
+            title_pos: None,
+            title_buf: None,
+            curved_title_char_bufs: Vec::new(),
+            network_opacity: 1.0,
+        }
+    }
+
+    pub fn with_center_items(mut self, center: bool) -> Self {
+        self.center_items = center;
+        self
+    }
+
+    pub fn with_title(mut self, title: &str) -> Self {
+        self.title = title.to_string();
+        self
+    }
+
+    pub fn with_item(mut self, label: &str, items: &[&str]) -> Self {
+        self.menu_items.push(label.to_string());
+        self.vertical_items.push(label.to_string());
+        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
+        self.menu_dropdown_checked.push(vec![None; items.len()]);
+
+        let item_strs: Vec<String> = items.iter().map(|s| s.to_string()).collect();
+        let mut menu = Menu::new(label, label, &item_strs);
+        menu.vertical = self.vertical;
+        self.menus.push(Box::new(menu));
+        self
+    }
+
+    pub fn with_item_vh(mut self, horizontal_label: &str, vertical_label: &str, items: &[&str]) -> Self {
+        self.menu_items.push(horizontal_label.to_string());
+        self.vertical_items.push(vertical_label.to_string());
+        self.menu_dropdowns.push(items.iter().map(|s| s.to_string()).collect());
+        self.menu_dropdown_checked.push(vec![None; items.len()]);
+
+        let item_strs: Vec<String> = items.iter().map(|s| s.to_string()).collect();
+        let mut menu = Menu::new(horizontal_label, vertical_label, &item_strs);
+        menu.vertical = self.vertical;
+        self.menus.push(Box::new(menu));
+        self
+    }
+
+    pub fn with_vertical(mut self, vertical: bool) -> Self {
+        self.vertical = vertical;
+        for menu in &mut self.menus {
+            menu.vertical = vertical;
+        }
+        self
+    }
+
+    pub fn with_z_index(mut self, z: i32) -> Self {
+        self.z_level = z;
+        self
+    }
+
+    fn item_y_vertical(&self, idx: usize) -> f32 {
+        let mut y = 8.0;
+        if !self.title.is_empty() {
+            y += 24.0;
+        }
+        y + idx as f32 * 24.0
+    }
+
+    fn item_h_vertical(&self) -> f32 {
+        24.0
+    }
+}
+
+impl Element for MenuBar {
+    fn rect(&self) -> (f32, f32, f32, f32) {
+        if !self.visible {
+            return (0.0, 0.0, 0.0, 0.0);
+        }
+        if self.vertical {
+            let total_h = if self.menus.is_empty() {
+                self.h
+            } else {
+                let last_idx = self.menus.len() - 1;
+                self.item_y_vertical(last_idx) + self.item_h_vertical()
+            };
+            (self.x, self.y, self.w, total_h)
+        } else {
+            (self.x, self.y, self.w, self.h)
+        }
+    }
+
+    fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
+        self.curved_circle = circle;
+        if circle.is_none() {
+            for menu in &mut self.menus {
+                menu.curved_arc = None;
+            }
+        }
+    }
+
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.x = x;
+        self.y = y;
+        self.w = w;
+        self.h = h;
+
+        let parent_ptr = self as *mut MenuBar as *mut (dyn Element + 'static);
+
+        if self.vertical {
+            let mut cy = 8.0;
+            if !self.title.is_empty() {
+                cy += 24.0;
+            }
+            for menu in &mut self.menus {
+                let ih = 24.0;
+                menu.set_rect(x, y + cy, w, ih);
+                menu.set_parent(Some(parent_ptr));
+                cy += ih;
+            }
+        } else {
+            if let Some((ccx, ccy, ccr)) = self.curved_circle {
+                let r_mid = ccr - h / 2.0;
+                let mut total_width = 8.0;
+                if !self.title.is_empty() {
+                    total_width += self.title.len() as f32 * 7.5 + 24.0;
+                }
+                for menu in &self.menus {
+                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+                }
+                
+                let total_angular_width = total_width / r_mid;
+                let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
+                let mut current_angle = start_angle;
+                
+                if !self.title.is_empty() {
+                    let title_w = self.title.len() as f32 * 7.5 + 24.0;
+                    let dtheta_title = title_w / r_mid;
+                    let theta_title = current_angle + dtheta_title / 2.0;
+                    
+                    let tx = ccx + r_mid * theta_title.cos() - title_w / 2.0 + 8.0;
+                    let ty = ccy + r_mid * theta_title.sin() - h / 2.0;
+                    self.title_pos = Some((tx, ty));
+                    current_angle += dtheta_title;
+                } else {
+                    self.title_pos = None;
+                }
+                
+                for menu in &mut self.menus {
+                    let iw = menu.active_title().len() as f32 * 7.5 + 16.0;
+                    let dtheta_menu = iw / r_mid;
+                    let theta_menu = current_angle + dtheta_menu / 2.0;
+                    
+                    let mx = ccx + r_mid * theta_menu.cos() - iw / 2.0;
+                    let my = ccy + r_mid * theta_menu.sin() - h / 2.0;
+                    
+                    menu.set_rect(mx, my, iw, h);
+                    menu.set_parent(Some(parent_ptr));
+                    menu.curved_arc = Some((ccx, ccy, ccr, h, current_angle, current_angle + dtheta_menu));
+                    current_angle += dtheta_menu;
+                }
+            } else {
+                self.title_pos = None;
+                let mut cx = 8.0;
+                if self.center_items {
+                    let mut total_width = 8.0;
+                    if !self.title.is_empty() {
+                        total_width += self.title.len() as f32 * 7.5 + 24.0;
+                    }
+                    for menu in &self.menus {
+                        total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+                    }
+                    if self.w > total_width {
+                        cx = (self.w - total_width) / 2.0;
+                    }
+                }
+                if !self.title.is_empty() {
+                    cx += self.title.len() as f32 * 7.5 + 24.0;
+                }
+                for menu in &mut self.menus {
+                    let iw = menu.active_title().len() as f32 * 7.5 + 16.0;
+                    menu.set_rect(x + cx, y, iw, h);
+                    menu.set_parent(Some(parent_ptr));
+                    cx += iw;
+                }
+            }
+        }
+    }
+
+    fn set_network_opacity(&mut self, opacity: f32) {
+        self.network_opacity = opacity;
+    }
+
+    fn color(&self) -> [f32; 4] {
+        if !self.visible {
+            [0.0, 0.0, 0.0, 0.0]
+        } else if self.focused {
+            let mut c = colors::PANEL_MENU_FOCUSED;
+            c[3] *= self.network_opacity;
+            c
+        } else {
+            let mut c = colors::PANEL_MENU_BG;
+            c[3] *= self.network_opacity;
+            c
+        }
+    }
+
+    fn set_hovered(&mut self, v: bool) {
+        self.hovering = v;
+    }
+
+    fn hovered(&self) -> bool {
+        self.hovering
+    }
+
+    fn hit_test(&self, px: f32, py: f32) -> bool {
+        if !self.visible {
+            return false;
+        }
+        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+            return false;
+        }
+        if let Some((ccx, ccy, ccr)) = self.curved_circle {
+            let dx = px - ccx;
+            let dy = py - ccy;
+            let dist = (dx * dx + dy * dy).sqrt();
+            if dist >= ccr - self.h && dist <= ccr {
+                let angle = dy.atan2(dx);
+                let mut norm_angle = angle;
+                if norm_angle < 0.0 {
+                    norm_angle += 2.0 * std::f32::consts::PI;
+                }
+                
+                let r_mid = ccr - self.h / 2.0;
+                let mut total_width = 8.0;
+                if !self.title.is_empty() {
+                    total_width += self.title.len() as f32 * 7.5 + 24.0;
+                }
+                for menu in &self.menus {
+                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+                }
+                let total_angular_width = total_width / r_mid;
+                let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
+                let end_angle = 1.5 * std::f32::consts::PI + total_angular_width / 2.0;
+                
+                if norm_angle >= start_angle && norm_angle <= end_angle {
+                    return true;
+                }
+            }
+            for menu in &self.menus {
+                if menu.hit_test(px, py) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        let (rx, ry, rw, rh) = self.rect();
+        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
+            return true;
+        }
+        for menu in &self.menus {
+            if menu.hit_test(px, py) {
+                return true;
+            }
+        }
+        false
+    }
+
+    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        if !self.visible {
+            return false;
+        }
+        let (rx, ry, rw, rh) = self.rect();
+        self.set_rect(rx, ry, rw, rh);
+
+        if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/home/lsgalante/Dropbox/Clear/debug.txt") {
+            use std::io::Write;
+            let _ = writeln!(f, "MenuBar::on_cursor_moved px={}, py={} curved={:?} rect={:?}", px, py, self.curved_circle, (rx, ry, rw, rh));
+        }
+
+        let mut changed = false;
+        self.hovered_menu = None;
+        for (idx, menu) in self.menus.iter_mut().enumerate() {
+            if menu.cursor_moved(px, py) {
+                changed = true;
+            }
+            if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open("/home/lsgalante/Dropbox/Clear/debug.txt") {
+                use std::io::Write;
+                let _ = writeln!(f, "  Menu[{}] active_title={} curved={:?} hovered={} hit={}", idx, menu.active_title(), menu.curved_arc, menu.hovered(), menu.hit_test(px, py));
+            }
+            if menu.hovered() {
+                self.hovered_menu = Some(idx);
+            }
+        }
+        changed
+    }
+
+    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
+        if !self.visible {
+            return false;
+        }
+        let (rx, ry, rw, rh) = self.rect();
+        self.set_rect(rx, ry, rw, rh);
+
+        let mut changed = false;
+        for menu in &mut self.menus {
+            let res = menu.mouse_input(button, state, px, py);
+            if res {
+                changed = true;
+            }
+        }
+        if !self.is_menu_open() {
+            self.unfocus();
+        }
+        changed
+    }
+
+    fn focus(&mut self) {
+        if self.is_menu_open() {
+            self.focused = true;
+            for menu in &mut self.menus {
+                if menu.is_menu_open() {
+                    menu.focus();
+                    return;
+                }
+            }
+        } else {
+            self.focused = false;
+            focus::clear_if_matches(self);
+            return;
+        }
+        self.focused = true;
+        focus::set_focused(self);
+    }
+
+    fn unfocus(&mut self) {
+        self.focused = false;
+        focus::clear_if_matches(self);
+        for menu in &mut self.menus {
+            menu.unfocus();
+        }
+    }
+
+    fn focused(&self) -> bool {
+        self.focused || self.is_menu_open()
+    }
+
+    fn set_selected(&mut self, selected: bool) {
+        self.focused = selected;
+        if !selected {
+            for menu in &mut self.menus {
+                menu.set_selected(false);
+            }
+        }
+    }
+
+    fn set_modifiers(&mut self, ctrl: bool, shift: bool, alt: bool) {
+        for menu in &mut self.menus {
+            menu.set_modifiers(ctrl, shift, alt);
+        }
+    }
+
+    fn menu_names(&self) -> Vec<String> {
+        self.menu_items.clone()
+    }
+
+    fn menu_click(&mut self) -> Option<(usize, usize)> {
+        for (idx, menu) in self.menus.iter_mut().enumerate() {
+            if let Some((_, item_idx)) = menu.menu_click() {
+                return Some((idx, item_idx));
+            }
+        }
+        None
+    }
+
+    fn set_item_checked(&mut self, menu_idx: usize, item_idx: usize, checked: bool) {
+        if let Some(menu) = self.menu_dropdown_checked.get_mut(menu_idx) {
+            if item_idx < menu.len() {
+                menu[item_idx] = Some(checked);
+            }
+        }
+        if let Some(menu) = self.menus.get_mut(menu_idx) {
+            menu.set_item_checked(0, item_idx, checked);
+        }
+    }
+
+    fn set_menu_items(&mut self, menu_idx: usize, items: &[String]) {
+        if menu_idx < self.menu_dropdowns.len() {
+            self.menu_dropdowns[menu_idx] = items.to_vec();
+            self.menu_dropdown_checked[menu_idx] = vec![Some(false); items.len()];
+        }
+        if let Some(menu) = self.menus.get_mut(menu_idx) {
+            menu.items = items.to_vec();
+            menu.item_checked = vec![Some(false); items.len()];
+            menu.item_bufs.clear();
+        }
+    }
+
+    fn is_menu_bar(&self) -> bool {
+        self.visible
+    }
+
+    fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> {
+        if !self.visible {
+            return None;
+        }
+        for (idx, menu) in self.menus.iter().enumerate() {
+            if menu.hit_test(px, py) {
+                let mut formatted_items = Vec::new();
+                for (i, item) in menu.items.iter().enumerate() {
+                    let checked = menu.item_checked.get(i).and_then(|&v| v);
+                    let prefix = match checked {
+                        Some(true) => "✓ ",
+                        Some(false) => "  ",
+                        None => "",
+                    };
+                    formatted_items.push(format!("{}{}", prefix, item));
+                }
+                return Some((idx, menu.active_title().to_string(), formatted_items, menu.base.x, menu.base.y, menu.base.w, menu.base.h));
+            }
+        }
+        None
+    }
+
+    fn trigger_menu_click(&mut self, menu_idx: usize, item_idx: usize) {
+        if let Some(menu) = self.menus.get_mut(menu_idx) {
+            menu.clicked_item = Some(item_idx);
+        }
+    }
+
+    fn is_menu_open(&self) -> bool {
+        self.visible && self.menus.iter().any(|m| m.is_menu_open())
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut quads = Vec::new();
+        for menu in &self.menus {
+            quads.extend(menu.all_quads());
+        }
+        quads
+    }
+
+    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut arcs = Vec::new();
+        for menu in &self.menus {
+            arcs.extend(menu.extra_arcs());
+        }
+        arcs
+    }
+
+    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+        if !self.visible {
+            return;
+        }
+        if !self.title.is_empty() {
+            if let Some((_ccx, _ccy, _ccr)) = self.curved_circle {
+                if self.curved_title_char_bufs.len() != self.title.chars().count() {
+                    self.curved_title_char_bufs = self.title.chars()
+                        .map(|c| make_widget_text_buffer(fs, &c.to_string(), 12.0, "Outfit"))
+                        .collect();
+                }
+                self.title_buf = None;
+            } else {
+                if self.title_buf.is_none() {
+                    self.title_buf = Some(make_widget_text_buffer(fs, &self.title, 12.0, "Outfit"));
+                }
+                self.curved_title_char_bufs.clear();
+            }
+        } else {
+            self.title_buf = None;
+            self.curved_title_char_bufs.clear();
+        }
+        for menu in &mut self.menus {
+            menu.prepare_text(fs);
+        }
+    }
+
+    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut items = Vec::new();
+        let color = glyphon::Color::rgb(0xaa, 0xaa, 0xbb);
+
+        if let Some((ccx, ccy, ccr)) = self.curved_circle {
+            let r_mid = ccr - self.h / 2.0;
+            let mut total_width = 8.0;
+            if !self.title.is_empty() {
+                total_width += self.title.len() as f32 * 7.5 + 24.0;
+            }
+            for menu in &self.menus {
+                total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+            }
+            let total_angular_width = total_width / r_mid;
+            let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
+            let current_angle = start_angle;
+
+            if !self.title.is_empty() {
+                let title_w = self.title.len() as f32 * 7.5 + 24.0;
+                let dtheta_title = title_w / r_mid;
+
+                let char_widths: Vec<f32> = self.title.chars().map(|c| {
+                    TextLabel::estimate_width(&c.to_string(), 12.0)
+                }).collect();
+                let total_chars_width: f32 = char_widths.iter().sum();
+                let mid_angle = (current_angle + current_angle + dtheta_title) / 2.0;
+                let angular_width = total_chars_width / r_mid;
+                let text_start_angle = mid_angle - angular_width / 2.0;
+                let mut cur_char_angle = text_start_angle;
+
+                for (char_idx, c_buf) in self.curved_title_char_bufs.iter().enumerate() {
+                    let cw = char_widths[char_idx];
+                    let dtheta = cw / r_mid;
+                    let char_center_angle = cur_char_angle + dtheta / 2.0;
+
+                    let tx = ccx + r_mid * char_center_angle.cos() - cw / 2.0;
+                    let ty = ccy + r_mid * char_center_angle.sin() - 12.0 / 2.0;
+
+                    items.push((c_buf, tx, ty, color));
+                    cur_char_angle += dtheta;
+                }
+            }
+        } else {
+            let mut start_x = 8.0;
+            if self.center_items {
+                let mut total_width = 8.0;
+                if !self.title.is_empty() {
+                    total_width += self.title.len() as f32 * 7.5 + 24.0;
+                }
+                for menu in &self.menus {
+                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+                }
+                if self.w > total_width {
+                    start_x = (self.w - total_width) / 2.0;
+                }
+            }
+            if let Some(ref title_buf) = self.title_buf {
+                items.push((title_buf, self.x + start_x, self.y + 7.0, color));
+            }
+        }
+
+        for menu in &self.menus {
+            items.extend(menu.get_text_items());
+        }
+        items
+    }
+
+    fn text_labels(&self) -> Vec<TextLabel> {
+        if !self.visible {
+            return Vec::new();
+        }
+        let mut labels = Vec::new();
+        if let Some((ccx, ccy, ccr)) = self.curved_circle {
+            let r_mid = ccr - self.h / 2.0;
+            let mut total_width = 8.0;
+            if !self.title.is_empty() {
+                total_width += self.title.len() as f32 * 7.5 + 24.0;
+            }
+            for menu in &self.menus {
+                total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+            }
+            let total_angular_width = total_width / r_mid;
+            let start_angle = 1.5 * std::f32::consts::PI - total_angular_width / 2.0;
+            let current_angle = start_angle;
+
+            if !self.title.is_empty() {
+                let title_w = self.title.len() as f32 * 7.5 + 24.0;
+                let dtheta_title = title_w / r_mid;
+                labels.extend(TextLabel::curved_layout(
+                    &self.title,
+                    ccx, ccy, r_mid,
+                    current_angle, current_angle + dtheta_title,
+                    12.0,
+                    [0xaa, 0xaa, 0xbb],
+                ));
+            }
+        } else {
+            let mut start_x = 8.0;
+            if self.center_items {
+                let mut total_width = 8.0;
+                if !self.title.is_empty() {
+                    total_width += self.title.len() as f32 * 7.5 + 24.0;
+                }
+                for menu in &self.menus {
+                    total_width += menu.active_title().len() as f32 * 7.5 + 16.0;
+                }
+                if self.w > total_width {
+                    start_x = (self.w - total_width) / 2.0;
+                }
+            }
+            if !self.title.is_empty() {
+                labels.push(TextLabel {
+                    text: self.title.clone(),
+                    x: self.x + start_x,
+                    y: self.y + 7.0,
+                    font_size: 12.0,
+                    color: [0xaa, 0xaa, 0xbb],
+                });
+            }
+        }
+        for menu in &self.menus {
+            labels.extend(menu.text_labels());
+        }
+        labels
+    }
+
+    fn set_visible(&mut self, visible: bool) {
+        self.visible = visible;
+        for menu in &mut self.menus {
+            menu.set_visible(visible);
+        }
+    }
+
+    fn visible(&self) -> bool {
+        self.visible
+    }
+
+    fn children(&self) -> Vec<*mut (dyn Element + 'static)> {
+        self.menus.iter().map(|m| {
+            let ptr: *const dyn Element = &**m as &dyn Element;
+            ptr as *mut (dyn Element + 'static)
+        }).collect()
+    }
+
+    fn z_index(&self) -> i32 {
+        self.z_level
+    }
+
+    fn set_center_items(&mut self, center: bool) {
+        self.center_items = center;
+    }
+
+    fn menu_items_list(&self) -> Vec<Vec<String>> {
+        self.menu_dropdowns.clone()
+    }
+
+    fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
+        self.menu_dropdown_checked.clone()
+    }
+}
+
+impl Drop for MenuBar {
+    fn drop(&mut self) {
+        focus::clear_if_matches(self);
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Menu {
+    pub base: Widget,
+    pub title: String,
+    pub vertical_title: String,
+    pub items: Vec<String>,
+    pub item_checked: Vec<Option<bool>>,
+    pub open: bool,
+    pub vertical: bool,
+    hovered_item: Option<usize>,
+    clicked_item: Option<usize>,
+    was_open: Option<usize>,
+    pub parent: Option<*mut (dyn Element + 'static)>,
+    pub children: Vec<*mut (dyn Element + 'static)>,
+    pub curved_arc: Option<(f32, f32, f32, f32, f32, f32)>,
+    pub title_buf: Option<glyphon::Buffer>,
+    pub item_bufs: Vec<glyphon::Buffer>,
+    pub check_buf: Option<glyphon::Buffer>,
+    pub curved_char_bufs: Vec<glyphon::Buffer>,
+}
+
+impl Menu {
+    pub fn new(title: &str, vertical_title: &str, items: &[String]) -> Self {
+        Self {
+            base: Widget::new(),
+            title: title.to_string(),
+            vertical_title: vertical_title.to_string(),
+            items: items.to_vec(),
+            item_checked: vec![None; items.len()],
+            open: false,
+            vertical: false,
+            hovered_item: None,
+            clicked_item: None,
+            was_open: None,
+            parent: None,
+            children: Vec::new(),
+            curved_arc: None,
+            title_buf: None,
+            item_bufs: Vec::new(),
+            check_buf: None,
+            curved_char_bufs: Vec::new(),
+        }
+    }
+
+    pub fn active_title(&self) -> &str {
+        if self.vertical {
+            &self.vertical_title
+        } else {
+            &self.title
+        }
+    }
+
+    fn dropdown_rect(&self) -> (f32, f32, f32, f32) {
+        let dh = self.items.len() as f32 * DROPDOWN_ITEM_H;
+        let mut max_len = 0;
+        for item in &self.items {
+            max_len = max_len.max(item.len());
+        }
+        let dw = (max_len as f32 * 7.5 + 40.0).max(120.0);
+        let dx = if self.vertical {
+            self.base.x + self.base.w
+        } else {
+            self.base.x
+        };
+        let dy = if self.vertical {
+            self.base.y
+        } else {
+            self.base.y + self.base.h
+        };
+        (dx, dy, dw, dh)
+    }
+}
+impl Element for Menu {
+    crate::impl_widget_base!(Menu);
+
+    fn label(&self) -> Option<String> {
+        Some(self.active_title().to_string())
+    }
+
+    fn color(&self) -> [f32; 4] {
+        [0.0, 0.0, 0.0, 0.0]
+    }
+
+    fn hit_test(&self, px: f32, py: f32) -> bool {
+        if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+            return false;
+        }
+        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
+            let dx = px - cx;
+            let dy = py - cy;
+            let dist = (dx * dx + dy * dy).sqrt();
+            if dist >= r - thickness && dist <= r {
+                let angle = dy.atan2(dx);
+                let mut norm_angle = angle;
+                if norm_angle < 0.0 {
+                    norm_angle += 2.0 * std::f32::consts::PI;
+                }
+                if norm_angle >= start_angle && norm_angle <= end_angle {
+                    return true;
+                }
+            }
+            if self.open {
+                let (dx, dy, dw, dh) = self.dropdown_rect();
+                if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                    return true;
+                }
+            }
+            return false;
+        }
+        let (rx, ry, rw, rh) = self.rect();
+        if px >= rx && px <= rx + rw && py >= ry && py <= ry + rh {
+            return true;
+        }
+        if self.open {
+            let (dx, dy, dw, dh) = self.dropdown_rect();
+            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                return true;
+            }
+        }
+        false
+    }
+
+    fn on_cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        self.was_open = None;
+        let was_hovering = self.base.hovered;
+        self.base.hovered = self.hit_test(px, py);
+        let old_item = self.hovered_item;
+        self.hovered_item = None;
+
+        if self.open {
+            let (dx, dy, dw, dh) = self.dropdown_rect();
+            if px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                if di < self.items.len() {
+                    self.hovered_item = Some(di);
+                }
+            }
+        }
+
+        was_hovering != self.base.hovered || old_item != self.hovered_item
+    }
+
+    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
+        if button != MouseButton::Left || state != ElementState::Pressed {
+            return false;
+        }
+        if !self.hit_test(px, py) {
+            return false;
+        }
+
+        // Check dropdown click if open
+        if self.open {
+            let (dx, dy, dw, dh) = self.dropdown_rect();
+            if dh > 0.0 && px >= dx && px < dx + dw && py >= dy && py < dy + dh {
+                let di = ((py - dy) / DROPDOWN_ITEM_H) as usize;
+                if di < self.items.len() {
+                    self.clicked_item = Some(di);
+                    self.open = false;
+                    return true;
+                }
+            }
+        }
+
+        // Since we passed hit_test and didn't click dropdown, it's a click on the header title
+        if self.was_open == Some(0) || self.open {
+            self.open = false;
+            self.was_open = None;
+        } else {
+            self.open = true;
+            self.was_open = None;
+            focus::set_focused(self);
+        }
+        true
+    }
+
+    fn focus(&mut self) {
+        self.open = true;
+        self.base.focused = true;
+        focus::set_focused(self);
+    }
+
+    fn unfocus(&mut self) {
+        if self.open {
+            self.was_open = Some(0);
+        }
+        self.open = false;
+        self.base.focused = false;
+        focus::clear_if_matches(self);
+        self.hovered_item = None;
+    }
+
+    fn set_selected(&mut self, selected: bool) {
+        self.base.focused = selected;
+        if !selected {
+            self.open = false;
+            self.was_open = None;
+            self.hovered_item = None;
+            focus::clear_if_matches(self);
+        }
+    }
+
+    fn menu_click(&mut self) -> Option<(usize, usize)> {
+        self.clicked_item.take().map(|i| (0, i))
+    }
+
+    fn set_item_checked(&mut self, _menu_idx: usize, item_idx: usize, checked: bool) {
+        if item_idx < self.item_checked.len() {
+            self.item_checked[item_idx] = Some(checked);
+            self.item_bufs.clear();
+        }
+    }
+
+    fn is_menu_open(&self) -> bool {
+        self.open
+    }
+
+    fn highlight_quad(&self) -> Option<(f32, f32, f32, f32, [f32; 4])> {
+        if self.curved_arc.is_some() {
+            None
+        } else {
+            let hc = self.highlight_color()?;
+            Some((self.base.x, self.base.y, self.base.w, self.base.h, hc))
+        }
+    }
+
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        let mut quads = Vec::new();
+        if self.open {
+            let (dx, dy, dw, dh) = self.dropdown_rect();
+            if dh > 0.0 {
+                quads.push((dx, dy, dw, dh, colors::PANEL_MENU_BG));
+                if let Some(di) = self.hovered_item {
+                    quads.push((dx, dy + di as f32 * DROPDOWN_ITEM_H, dw, DROPDOWN_ITEM_H, colors::PANEL_MENU_HOVER));
+                }
+            }
+        }
+        quads
+    }
+
+    fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
+        let mut arcs = Vec::new();
+        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
+            if self.base.hovered && !self.open {
+                arcs.push((cx, cy, r, thickness, start_angle, end_angle, colors::PANEL_MENU_HOVER));
+            }
+        }
+        arcs
+    }
+
+    fn text_labels(&self) -> Vec<TextLabel> {
+        let mut labels = Vec::new();
+        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
+            let r_mid = r - thickness / 2.0;
+            labels.extend(TextLabel::curved_layout(
+                &self.active_title(),
+                cx, cy, r_mid,
+                start_angle, end_angle,
+                12.0,
+                [0xcc, 0xcc, 0xd4],
+            ));
+        } else {
+            labels.push(TextLabel {
+                text: self.active_title().to_string(),
+                x: self.base.x + 8.0,
+                y: self.base.y + 7.0,
+                font_size: 12.0,
+                color: [0xcc, 0xcc, 0xd4],
+            });
+        }
+        if self.open {
+            let (dx, dy, _, _) = self.dropdown_rect();
+            for (i, item) in self.items.iter().enumerate() {
+                let checked = self.item_checked.get(i).and_then(|&v| v);
+                let prefix = match checked {
+                    Some(true) => "\u{2713} ",
+                    Some(false) => "  ",
+                    None => "",
+                };
+                labels.push(TextLabel {
+                    text: format!("{}{}", prefix, item),
+                    x: dx + 8.0,
+                    y: dy + i as f32 * DROPDOWN_ITEM_H + 5.0,
+                    font_size: 12.0,
+                    color: [0xcc, 0xcc, 0xd4],
+                });
+            }
+        }
+        labels
+    }
+
+    fn set_visible(&mut self, visible: bool) {
+        self.base.hovered = false;
+        if !visible {
+            self.open = false;
+            self.was_open = None;
+            self.hovered_item = None;
+            focus::clear_if_matches(self);
+        }
+    }
+
+    fn parent(&self) -> Option<*mut (dyn Element + 'static)> {
+        self.parent
+    }
+
+    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>) {
+        self.parent = parent;
+    }
+
+    fn z_index(&self) -> i32 {
+        100
+    }
+
+    fn menu_items(&self) -> Vec<String> {
+        self.items.clone()
+    }
+
+    fn menu_item_checked(&self) -> Vec<Option<bool>> {
+        self.item_checked.clone()
+    }
+
+    fn is_vertical(&self) -> bool {
+        self.vertical
+    }
+
+    fn focused(&self) -> bool {
+        self.base.focused
+    }
+
+    fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+        let title_text = self.active_title();
+        if let Some((_cx, _cy, _r, _thickness, _start_angle, _end_angle)) = self.curved_arc {
+            if self.curved_char_bufs.len() != title_text.chars().count() {
+                self.curved_char_bufs = title_text.chars()
+                    .map(|c| make_widget_text_buffer(fs, &c.to_string(), 12.0, "Outfit"))
+                    .collect();
+            }
+            self.title_buf = None;
+        } else {
+            if self.title_buf.is_none() {
+                self.title_buf = Some(make_widget_text_buffer(fs, title_text, 12.0, "Outfit"));
+            }
+            self.curved_char_bufs.clear();
+        }
+
+        if self.open {
+            if self.item_bufs.len() != self.items.len() {
+                self.item_bufs = self.items.iter().enumerate().map(|(i, item)| {
+                    let checked = self.item_checked.get(i).and_then(|&v| v);
+                    let prefix = match checked {
+                        Some(true) => "\u{2713} ",
+                        Some(false) => "  ",
+                        None => "",
+                    };
+                    let text = format!("{}{}", prefix, item);
+                    make_widget_text_buffer(fs, &text, 12.0, "Outfit")
+                }).collect();
+            }
+        } else {
+            self.item_bufs.clear();
+        }
+    }
+
+    fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
+        let mut items = Vec::new();
+        let color = glyphon::Color::rgb(0xcc, 0xcc, 0xd4);
+
+        if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
+            let r_mid = r - thickness / 2.0;
+            let active_title = self.active_title();
+            
+            let char_widths: Vec<f32> = active_title.chars().map(|c| {
+                TextLabel::estimate_width(&c.to_string(), 12.0)
+            }).collect();
+            let total_chars_width: f32 = char_widths.iter().sum();
+            
+            let angular_width = total_chars_width / r_mid;
+            let text_start_angle = (start_angle + end_angle) / 2.0 - angular_width / 2.0;
+            let mut cur_char_angle = text_start_angle;
+            
+            for (char_idx, c_buf) in self.curved_char_bufs.iter().enumerate() {
+                if char_idx < char_widths.len() {
+                    let cw = char_widths[char_idx];
+                    let dtheta = cw / r_mid;
+                    let char_center_angle = cur_char_angle + dtheta / 2.0;
+                    
+                    let tx = cx + r_mid * char_center_angle.cos() - cw / 2.0;
+                    let ty = cy + r_mid * char_center_angle.sin() - 12.0 / 2.0;
+                    
+                    items.push((c_buf, tx, ty, color));
+                    cur_char_angle += dtheta;
+                }
+            }
+        } else {
+            if let Some(ref title_bu
diff truncated