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

commite2444351aa2d607b9a4f47c704d00d5f1ed07d3c
parent10b1720cd3
authorLucas Galante <[email protected]>
date2026-07-11 22:46
refactor(ramp)!: Ramp + ColorRamp onto the narrow traits — every widget is across (Phase 6as)

Both composites take the TreeList shape: rect_assigned arranges the field
widgets; register_embedded_children keeps them ctx-linked for event
propagation; paints_own_subtree + paint() emit the whole subtree (grid,
curve/gradient, key handles, own control labels, and the field children
through their adapters); the mouse/cursor/key/tick bodies move to
on_event/tick_ctx with the routing ui; the open dropdown popover extends
Input::hit (the 5p pattern); tick_ctx re-parents the field widgets to the
adapter each tick (their label fade blends against the parent's color —
the legacy set_rect re-parenting, now with the adapter pointer).

Adapted::paint_self grows a subtree pass-through: paints_own_subtree
widgets author their COMPLETE text in paint(), so their Text prims forward
verbatim (per-child fonts + bounds) instead of being flattened to
widget_font by the own-labels re-derivation — caught by the gallery A/B
(Ramp's mixed-font labels rendered serif), and TreeList stays AE=0 through
the new path.

Verified: test-interface gallery diff = two cursors; data-editor loaded
tree AE=0; 176 tests; full workspace builds.

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

 src/widget/input/dropdown.rs |    2 +-
 src/widget/input/ramp.rs     | 1281 +++++++++++++++++++++++-------------------
 src/widget/model.rs          |   11 +
 3 files changed, 705 insertions(+), 589 deletions(-)

diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 140ca8b..446a29f 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -959,7 +959,7 @@ mod tests {
         dd.set_rect(30.0, 125.0, 110.0, 20.0);
 
         // Link the dropdown parent pointer to the Ramp (the legacy direct-write path)
-        dd.parent = Some(&mut ramp as *mut crate::widget::Ramp as *mut (dyn crate::widget::Element + 'static));
+        dd.parent = Some(crate::widget::Element::as_ptr_mut(&mut ramp));
 
         // Compute geometry
         let (rx, ry, rw, rh) = dd.get_popover_geom();
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index b207eba..ebf00fd 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -1,4 +1,7 @@
 use crate::colors;
+use crate::scene::layout::{Rect, Size};
+use crate::scene::paint::PaintCtx;
+use crate::widget::model::{EventCtx, Input, Layout, Paint};
 use crate::widget::*;
 use crate::widget::input::{Slider, Button};
 
@@ -25,11 +28,10 @@ pub struct ColorRamp {
     pub b_slider: Adapted<Slider>,
     pub del_button: Adapted<Button>,
     
-    pub parent: Option<*mut (dyn Element + 'static)>,
 }
 
 impl ColorRamp {
-    pub fn new() -> Self {
+    pub fn new() -> Adapted<ColorRamp> {
         let keys = vec![
             ColorRampKey { pos: 0.0, color: [0.0, 0.0, 0.0] },
             ColorRampKey { pos: 1.0, color: [1.0, 1.0, 1.0] },
@@ -40,7 +42,7 @@ impl ColorRamp {
         let b_slider = Slider::new().with_label("Blue");
         let del_button = Button::new(0.0, 0.0, 70.0, 28.0).with_label("Delete Key");
         
-        Self {
+        Adapted::new(ColorRamp {
             base: Widget::new(),
             keys,
             selected_key_idx: None,
@@ -50,8 +52,7 @@ impl ColorRamp {
             g_slider,
             b_slider,
             del_button,
-            parent: None,
-        }
+        })
     }
     
     pub fn get_interpolated_color(&self, t: f32) -> [f32; 3] {
@@ -95,302 +96,7 @@ impl ColorRamp {
     }
 }
 
-impl Element for ColorRamp {
-    crate::impl_widget_base!(ColorRamp);
-    
-    fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
-        let mut changed = self.just_changed;
-        self.just_changed = false;
-        
-        if self.selected_key_idx.is_some() {
-            if self.r_slider.tick(dt, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[0] = self.r_slider.inner().value();
-                }
-                changed = true;
-            }
-            if self.g_slider.tick(dt, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[1] = self.g_slider.inner().value();
-                }
-                changed = true;
-            }
-            if self.b_slider.tick(dt, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[2] = self.b_slider.inner().value();
-                }
-                changed = true;
-            }
-            if self.del_button.tick(dt, ctx) {
-                changed = true;
-            }
-        }
-        changed
-    }
-    
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
-        self.parent = parent;
-    }
-    
-    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        let self_ptr = self as *const Self as *mut Self;
-        unsafe {
-            vec![
-                (*self_ptr).r_slider.as_ptr_mut(),
-                (*self_ptr).g_slider.as_ptr_mut(),
-                (*self_ptr).b_slider.as_ptr_mut(),
-                (*self_ptr).del_button.as_ptr_mut(),
-            ]
-        }
-    }
-    
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        if ctx.is_focused(&self.r_slider) {
-            return self.r_slider.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.g_slider) {
-            return self.g_slider.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.b_slider) {
-            return self.b_slider.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.del_button) {
-            return self.del_button.keyboard_input(event, ctx);
-        }
-        false
-    }
-    
-    fn color(&self) -> [f32; 4] {
-        colors::ramp_background_color()
-    }
-    
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.base.x = x;
-        self.base.y = y;
-        self.base.w = w;
-        self.base.h = h;
-        
-        let self_ptr = self.as_ptr_mut();
-        let mut dummy = crate::context::UiContext::new();
-        self.r_slider.set_parent(Some(self_ptr), &mut dummy);
-        self.g_slider.set_parent(Some(self_ptr), &mut dummy);
-        self.b_slider.set_parent(Some(self_ptr), &mut dummy);
-        self.del_button.set_parent(Some(self_ptr), &mut dummy);
-        
-        let th = crate::layout::ramp_height();
-        let sy = y + th + 55.0;
-        let slider_w = w - 100.0;
-        
-        if self.selected_key_idx.is_some() {
-            self.r_slider.set_rect(x + 10.0, sy, slider_w, 20.0);
-            self.g_slider.set_rect(x + 10.0, sy + 25.0, slider_w, 20.0);
-            self.b_slider.set_rect(x + 10.0, sy + 50.0, slider_w, 20.0);
-            self.del_button.set_rect(x + w - 80.0, sy + 20.0, 70.0, 28.0);
-        } else {
-            self.r_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
-            self.g_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
-            self.b_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
-            self.del_button.set_rect(-1000.0, -1000.0, 0.0, 0.0);
-        }
-    }
-    
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        let mut quads = Vec::new();
-        let th = crate::layout::ramp_height();
-        let track_x = self.base.x + 10.0;
-        let track_w = self.base.w - 20.0;
-        
-        // Draw outer container border
-        let bx = self.base.x;
-        let by = self.base.y;
-        let bw = self.base.w;
-        let bh = self.base.h;
-        let border_color = colors::ramp_border_color();
-        quads.push((bx, by, bw, 1.0, border_color));                 // Top
-        quads.push((bx, by + bh - 1.0, bw, 1.0, border_color));         // Bottom
-        quads.push((bx, by, 1.0, bh, border_color));                 // Left
-        quads.push((bx + bw - 1.0, by, 1.0, bh, border_color));         // Right
-        
-        // Draw track border
-        quads.push((track_x - 1.0, self.base.y + 10.0 - 1.0, track_w + 2.0, th + 2.0, border_color));
-        
-        // Draw interpolated track slices (e.g. 100 slices)
-        let slices = 100;
-        let slice_w = track_w / slices as f32;
-        for i in 0..slices {
-            let t1 = i as f32 / slices as f32;
-            let t2 = (i + 1) as f32 / slices as f32;
-            let center_t = (t1 + t2) / 2.0;
-            let col = self.get_interpolated_color(center_t);
-            let sx = track_x + t1 * track_w;
-            quads.push((sx, self.base.y + 10.0, slice_w, th, [col[0], col[1], col[2], 1.0]));
-        }
-        
-        if self.selected_key_idx.is_some() {
-            let ctx_dummy = crate::context::UiContext::new();
-            quads.extend(self.r_slider.all_quads(&ctx_dummy));
-            quads.extend(self.g_slider.all_quads(&ctx_dummy));
-            quads.extend(self.b_slider.all_quads(&ctx_dummy));
-            quads.extend(self.del_button.all_quads(&ctx_dummy));
-        }
-        
-        quads
-    }
-    
-    fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> {
-        let mut circles = Vec::new();
-        let th = crate::layout::ramp_height();
-        let track_x = self.base.x + 10.0;
-        let track_w = self.base.w - 20.0;
-        let py = self.base.y + 10.0 + th + 15.0;
-        
-        for (idx, key) in self.keys.iter().enumerate() {
-            let cx = track_x + key.pos * track_w;
-            circles.push((cx, py, 7.0, [0.0, 0.0, 0.0, 0.8]));
-            circles.push((cx, py, 6.0, [key.color[0], key.color[1], key.color[2], 1.0]));
-            if Some(idx) == self.selected_key_idx {
-                circles.push((cx, py, 8.0, [0.49, 1.0, 1.0, 0.5]));
-            }
-        }
-        
-        circles
-    }
-    
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py_event: f32, ctx: &mut UiContext) -> bool {
-        if button != MouseButton::Left { return false; }
-        
-        let th = crate::layout::ramp_height();
-        let track_x = self.base.x + 10.0;
-        let track_w = self.base.w - 20.0;
-        let py_peg = self.base.y + 10.0 + th + 15.0;
-        
-        if state == ElementState::Pressed {
-            for (idx, key) in self.keys.iter().enumerate() {
-                let cx = track_x + key.pos * track_w;
-                let dx = px - cx;
-                let dy = py_event - py_peg;
-                if (dx*dx + dy*dy) <= 64.0 {
-                    self.selected_key_idx = Some(idx);
-                    self.is_dragging_key = true;
-                    self.r_slider.set_value(key.color[0]);
-                    self.g_slider.set_value(key.color[1]);
-                    self.b_slider.set_value(key.color[2]);
-                    self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
-                    return true;
-                }
-            }
-            
-            if px >= track_x && px <= track_x + track_w && py_event >= self.base.y + 10.0 && py_event <= self.base.y + 10.0 + th {
-                let t = (px - track_x) / track_w;
-                let col = self.get_interpolated_color(t);
-                let new_key = ColorRampKey { pos: t, color: col };
-                self.keys.push(new_key);
-                self.sort_keys();
-                self.just_changed = true;
-                
-                if let Some(new_idx) = self.keys.iter().position(|k| (k.pos - t).abs() < 0.0001) {
-                    self.selected_key_idx = Some(new_idx);
-                    self.r_slider.set_value(col[0]);
-                    self.g_slider.set_value(col[1]);
-                    self.b_slider.set_value(col[2]);
-                }
-                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
-                return true;
-            }
-            
-            if self.selected_key_idx.is_some() {
-                if self.r_slider.mouse_input(button, state, px, py_event, ctx) { return true; }
-                if self.g_slider.mouse_input(button, state, px, py_event, ctx) { return true; }
-                if self.b_slider.mouse_input(button, state, px, py_event, ctx) { return true; }
-                if self.del_button.mouse_input(button, state, px, py_event, ctx) {
-                    if self.del_button.take_click() {
-                        if let Some(idx) = self.selected_key_idx {
-                            if self.keys.len() > 2 {
-                                self.keys.remove(idx);
-                                self.selected_key_idx = None;
-                                self.just_changed = true;
-                                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
-                            }
-                        }
-                    }
-                    return true;
-                }
-            }
-        } else {
-            self.is_dragging_key = false;
-            if self.selected_key_idx.is_some() {
-                self.r_slider.mouse_input(button, state, px, py_event, ctx);
-                self.g_slider.mouse_input(button, state, px, py_event, ctx);
-                self.b_slider.mouse_input(button, state, px, py_event, ctx);
-                if self.del_button.mouse_input(button, state, px, py_event, ctx) {
-                    if self.del_button.take_click() {
-                        if let Some(idx) = self.selected_key_idx {
-                            if self.keys.len() > 2 {
-                                self.keys.remove(idx);
-                                self.selected_key_idx = None;
-                                self.just_changed = true;
-                                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
-                            }
-                        }
-                    }
-                }
-                return true;
-            }
-        }
-        false
-    }
-    
-    fn cursor_moved(&mut self, px: f32, py_event: f32, ctx: &mut UiContext) -> bool {
-        let mut changed = false;
-        let track_x = self.base.x + 10.0;
-        let track_w = self.base.w - 20.0;
-        
-        if self.is_dragging_key {
-            if let Some(idx) = self.selected_key_idx {
-                let t = ((px - track_x) / track_w).clamp(0.0, 1.0);
-                self.keys[idx].pos = t;
-                self.sort_keys();
-                changed = true;
-            }
-        }
-        
-        if self.selected_key_idx.is_some() {
-            if self.r_slider.cursor_moved(px, py_event, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[0] = self.r_slider.inner().value();
-                    changed = true;
-                }
-            }
-            if self.g_slider.cursor_moved(px, py_event, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[1] = self.g_slider.inner().value();
-                    changed = true;
-                }
-            }
-            if self.b_slider.cursor_moved(px, py_event, ctx) {
-                if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].color[2] = self.b_slider.inner().value();
-                    changed = true;
-                }
-            }
-            if self.del_button.cursor_moved(px, py_event, ctx) {
-                changed = true;
-            }
-        }
-        if changed {
-            self.just_changed = true;
-        }
-        changed
-    }
-    
-}
 
-impl Drop for ColorRamp {
-    fn drop(&mut self) {
-        clear_widget_references(self);
-    }
-}
 
 
 // ==========================================
@@ -416,11 +122,10 @@ pub struct Ramp {
     pub preset_dropdown: Adapted<Dropdown>,
     pub line_type_dropdown: Adapted<Dropdown>,
     
-    pub parent: Option<*mut (dyn Element + 'static)>,
 }
 
 impl Ramp {
-    pub fn new() -> Self {
+    pub fn new() -> Adapted<Ramp> {
         let keys = vec![
             RampKey { pos: 0.0, value: 0.5 },
             RampKey { pos: 0.2, value: 1.0 },
@@ -449,7 +154,7 @@ impl Ramp {
             0,
         ).with_open_upward(true);
         
-        Self {
+        Adapted::new(Ramp {
             base: Widget::new(),
             keys,
             selected_key_idx: None,
@@ -459,8 +164,7 @@ impl Ramp {
             del_button,
             preset_dropdown,
             line_type_dropdown,
-            parent: None,
-        }
+        })
     }
     
     pub fn apply_preset(&mut self, idx: usize) {
@@ -549,166 +253,488 @@ impl Ramp {
     }
 }
 
-impl Element for Ramp {
-    crate::impl_widget_base!(Ramp);
-    
-    fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        if let Some(pop_rect) = self.popover_rect() {
-            if px >= pop_rect.0 && px <= pop_rect.0 + pop_rect.2 && py >= pop_rect.1 && py <= pop_rect.1 + pop_rect.3 {
-                return true;
-            }
-        }
-        
-        let (x, y, w, h) = self.rect();
-        if w <= 0.0 || h <= 0.0 {
-            return false;
-        }
-        px >= x && px <= x + w && py >= y && py <= y + h
-    }
-    
-    fn tick(&mut self, dt: f32, ctx: &mut UiContext) -> bool {
-        let mut changed = self.just_changed;
-        self.just_changed = false;
-        
-        if self.preset_dropdown.tick(dt, ctx) {
-            let idx = self.preset_dropdown.selected;
-            self.apply_preset(idx);
-            changed = true;
+
+impl Ramp {
+    /// The ramp's OWN control labels (Preset / Line Type / Value-when-selected), each
+    /// with its control's font — shared by the legacy fonted getter and `paint_self`.
+    fn own_control_labels(&self) -> Vec<(TextLabel, Option<String>)> {
+        let mut labels = Vec::new();
+
+        let track_x = self.base.x + 10.0;
+        let track_w = self.base.w - 20.0;
+        let h = self.base.h;
+        let gh = (h - 70.0).max(30.0);
+        let sy = self.base.y + gh + 15.0;
+
+        let gap = 10.0;
+        let col_w = if self.selected_key_idx.is_some() {
+            let del_w = 70.0;
+            let available_for_inputs = track_w - del_w - 3.0 * gap;
+            (available_for_inputs / 3.0).max(40.0)
+        } else {
+            (track_w - gap) / 2.0
+        };
+
+        let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+        let label_color = colors::control_label_color_detached_u8();
+
+        labels.push((
+            TextLabel {
+                text: "Preset".to_string(),
+                x: track_x + 4.0,
+                y: sy - 2.0,
+                font_size,
+                color: label_color,
+            },
+            self.preset_dropdown.widget_font(),
+        ));
+
+        labels.push((
+            TextLabel {
+                text: "Line Type".to_string(),
+                x: track_x + col_w + gap + 4.0,
+                y: sy - 2.0,
+                font_size,
+                color: label_color,
+            },
+            self.line_type_dropdown.widget_font(),
+        ));
+
+        if self.selected_key_idx.is_some() {
+            labels.push((
+                TextLabel {
+                    text: "Value".to_string(),
+                    x: track_x + 2.0 * (col_w + gap) + 4.0,
+                    y: sy - 2.0,
+                    font_size,
+                    color: label_color,
+                },
+                self.val_slider.widget_font(),
+            ));
         }
+
+        labels
+    }
+}
+
+
+impl ColorRamp {
+    fn arrange_fields(&mut self) {
+        let (x, y, w, h) = (self.base.x, self.base.y, self.base.w, self.base.h);
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
         
-        if self.line_type_dropdown.tick(dt, ctx) {
-            changed = true;
+        
+        let th = crate::layout::ramp_height();
+        let sy = y + th + 55.0;
+        let slider_w = w - 100.0;
+        
+        if self.selected_key_idx.is_some() {
+            self.r_slider.set_rect(x + 10.0, sy, slider_w, 20.0);
+            self.g_slider.set_rect(x + 10.0, sy + 25.0, slider_w, 20.0);
+            self.b_slider.set_rect(x + 10.0, sy + 50.0, slider_w, 20.0);
+            self.del_button.set_rect(x + w - 80.0, sy + 20.0, 70.0, 28.0);
+        } else {
+            self.r_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.g_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.b_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.del_button.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+        }
+    
+    }
+}
+
+impl Layout for ColorRamp {
+    fn rect_assigned(&mut self, rect: Rect) {
+        let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
+        
+        
+        let th = crate::layout::ramp_height();
+        let sy = y + th + 55.0;
+        let slider_w = w - 100.0;
+        
+        if self.selected_key_idx.is_some() {
+            self.r_slider.set_rect(x + 10.0, sy, slider_w, 20.0);
+            self.g_slider.set_rect(x + 10.0, sy + 25.0, slider_w, 20.0);
+            self.b_slider.set_rect(x + 10.0, sy + 50.0, slider_w, 20.0);
+            self.del_button.set_rect(x + w - 80.0, sy + 20.0, 70.0, 28.0);
+        } else {
+            self.r_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.g_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.b_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.del_button.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+        }
+    
+    }
+
+    fn register_embedded_children(&mut self, host_id: WidgetId, ctx: &mut UiContext) {
+        let p = self.r_slider.as_ptr_mut();
+        let id = self.r_slider.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.g_slider.as_ptr_mut();
+        let id = self.g_slider.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.b_slider.as_ptr_mut();
+        let id = self.b_slider.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.del_button.as_ptr_mut();
+        let id = self.del_button.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+    }
+}
+
+impl Paint for ColorRamp {
+    fn color(&self) -> [f32; 4] {
+        colors::ramp_background_color()
+    }
+
+    // Field children are ctx-linked for event propagation but painted here (gated on a
+    // key being selected) — the walk must not also descend.
+    fn paints_own_subtree(&self) -> bool {
+        true
+    }
+
+    fn paint(&self, _rect: Rect, pc: &mut PaintCtx) {
+        let quads: Vec<(f32, f32, f32, f32, [f32; 4])> = {
+        let mut quads = Vec::new();
+        let th = crate::layout::ramp_height();
+        let track_x = self.base.x + 10.0;
+        let track_w = self.base.w - 20.0;
+        
+        // Draw outer container border
+        let bx = self.base.x;
+        let by = self.base.y;
+        let bw = self.base.w;
+        let bh = self.base.h;
+        let border_color = colors::ramp_border_color();
+        quads.push((bx, by, bw, 1.0, border_color));                 // Top
+        quads.push((bx, by + bh - 1.0, bw, 1.0, border_color));         // Bottom
+        quads.push((bx, by, 1.0, bh, border_color));                 // Left
+        quads.push((bx + bw - 1.0, by, 1.0, bh, border_color));         // Right
+        
+        // Draw track border
+        quads.push((track_x - 1.0, self.base.y + 10.0 - 1.0, track_w + 2.0, th + 2.0, border_color));
+        
+        // Draw interpolated track slices (e.g. 100 slices)
+        let slices = 100;
+        let slice_w = track_w / slices as f32;
+        for i in 0..slices {
+            let t1 = i as f32 / slices as f32;
+            let t2 = (i + 1) as f32 / slices as f32;
+            let center_t = (t1 + t2) / 2.0;
+            let col = self.get_interpolated_color(center_t);
+            let sx = track_x + t1 * track_w;
+            quads.push((sx, self.base.y + 10.0, slice_w, th, [col[0], col[1], col[2], 1.0]));
+        }
+        
+        if self.selected_key_idx.is_some() {
+            let ctx_dummy = crate::context::UiContext::new();
+            quads.extend(self.r_slider.all_quads(&ctx_dummy));
+            quads.extend(self.g_slider.all_quads(&ctx_dummy));
+            quads.extend(self.b_slider.all_quads(&ctx_dummy));
+            quads.extend(self.del_button.all_quads(&ctx_dummy));
+        }
+        
+        quads
+    
+        };
+        for (qx, qy, qw, qh, qc) in quads {
+            pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+        }
+        let circles: Vec<(f32, f32, f32, [f32; 4])> = {
+        let mut circles = Vec::new();
+        let th = crate::layout::ramp_height();
+        let track_x = self.base.x + 10.0;
+        let track_w = self.base.w - 20.0;
+        let py = self.base.y + 10.0 + th + 15.0;
+        
+        for (idx, key) in self.keys.iter().enumerate() {
+            let cx = track_x + key.pos * track_w;
+            circles.push((cx, py, 7.0, [0.0, 0.0, 0.0, 0.8]));
+            circles.push((cx, py, 6.0, [key.color[0], key.color[1], key.color[2], 1.0]));
+            if Some(idx) == self.selected_key_idx {
+                circles.push((cx, py, 8.0, [0.49, 1.0, 1.0, 0.5]));
+            }
+        }
+        
+        circles
+    
+        };
+        for (cx, cy, r, c) in circles {
+            pc.circle(cx, cy, r, c);
+        }
+        if self.selected_key_idx.is_some() {
+            let dummy = UiContext::new();
+            self.r_slider.paint_self(&dummy, pc);
+            self.g_slider.paint_self(&dummy, pc);
+            self.b_slider.paint_self(&dummy, pc);
+            self.del_button.paint_self(&dummy, pc);
+        }
+    }
+}
+
+impl Input for ColorRamp {
+    fn wants_tick(&self) -> bool {
+        true
+    }
+
+    fn tick_ctx(&mut self, dt: f32, ectx: &mut EventCtx) -> bool {
+        // Keep the field widgets' tracked parent pointing at the adapter (their label
+        // fade blends against the parent's color; legacy set_rect re-parented them).
+        if let Some(host) = ectx.host_ptr() {
+            let mut dummy = crate::context::UiContext::new();
+            self.r_slider.set_parent(Some(host), &mut dummy);
+            self.g_slider.set_parent(Some(host), &mut dummy);
+            self.b_slider.set_parent(Some(host), &mut dummy);
+            self.del_button.set_parent(Some(host), &mut dummy);
         }
+        let Some(ui) = ectx.ui.as_deref_mut() else {
+            return false;
+        };
+        let mut changed = self.just_changed;
+        self.just_changed = false;
         
         if self.selected_key_idx.is_some() {
-            if self.val_slider.tick(dt, ctx) {
+            if self.r_slider.tick(dt, ui) {
                 if let Some(idx) = self.selected_key_idx {
-                    self.keys[idx].value = self.val_slider.inner().value();
-                    self.preset_dropdown.selected = 0; // Custom
+                    self.keys[idx].color[0] = self.r_slider.inner().value();
                 }
                 changed = true;
             }
-            if self.del_button.tick(dt, ctx) {
-                self.preset_dropdown.selected = 0; // Custom
+            if self.g_slider.tick(dt, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].color[1] = self.g_slider.inner().value();
+                }
+                changed = true;
+            }
+            if self.b_slider.tick(dt, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].color[2] = self.b_slider.inner().value();
+                }
+                changed = true;
+            }
+            if self.del_button.tick(dt, ui) {
                 changed = true;
             }
         }
         changed
-    }
     
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> { self.parent }
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
-        self.parent = parent;
     }
-    
-    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        let self_ptr = self as *const Self as *mut Self;
-        unsafe {
-            let mut list = vec![
-                (*self_ptr).preset_dropdown.as_ptr_mut(),
-                (*self_ptr).line_type_dropdown.as_ptr_mut(),
-            ];
+
+    fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
+        match event {
+            Event::MouseButton { button, state, x, y, .. } => {
+                let (button, state, px, py_event) = (*button, *state, *x, *y);
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
+        if button != MouseButton::Left { return false; }
+        
+        let th = crate::layout::ramp_height();
+        let track_x = self.base.x + 10.0;
+        let track_w = self.base.w - 20.0;
+        let py_peg = self.base.y + 10.0 + th + 15.0;
+        
+        if state == ElementState::Pressed {
+            for (idx, key) in self.keys.iter().enumerate() {
+                let cx = track_x + key.pos * track_w;
+                let dx = px - cx;
+                let dy = py_event - py_peg;
+                if (dx*dx + dy*dy) <= 64.0 {
+                    self.selected_key_idx = Some(idx);
+                    self.is_dragging_key = true;
+                    self.r_slider.set_value(key.color[0]);
+                    self.g_slider.set_value(key.color[1]);
+                    self.b_slider.set_value(key.color[2]);
+                    self.arrange_fields();
+                    return true;
+                }
+            }
+            
+            if px >= track_x && px <= track_x + track_w && py_event >= self.base.y + 10.0 && py_event <= self.base.y + 10.0 + th {
+                let t = (px - track_x) / track_w;
+                let col = self.get_interpolated_color(t);
+                let new_key = ColorRampKey { pos: t, color: col };
+                self.keys.push(new_key);
+                self.sort_keys();
+                self.just_changed = true;
+                
+                if let Some(new_idx) = self.keys.iter().position(|k| (k.pos - t).abs() < 0.0001) {
+                    self.selected_key_idx = Some(new_idx);
+                    self.r_slider.set_value(col[0]);
+                    self.g_slider.set_value(col[1]);
+                    self.b_slider.set_value(col[2]);
+                }
+                self.arrange_fields();
+                return true;
+            }
+            
             if self.selected_key_idx.is_some() {
-                list.push((*self_ptr).val_slider.as_ptr_mut());
-                list.push((*self_ptr).del_button.as_ptr_mut());
+                if self.r_slider.mouse_input(button, state, px, py_event, ui) { return true; }
+                if self.g_slider.mouse_input(button, state, px, py_event, ui) { return true; }
+                if self.b_slider.mouse_input(button, state, px, py_event, ui) { return true; }
+                if self.del_button.mouse_input(button, state, px, py_event, ui) {
+                    if self.del_button.take_click() {
+                        if let Some(idx) = self.selected_key_idx {
+                            if self.keys.len() > 2 {
+                                self.keys.remove(idx);
+                                self.selected_key_idx = None;
+                                self.just_changed = true;
+                                self.arrange_fields();
+                            }
+                        }
+                    }
+                    return true;
+                }
+            }
+        } else {
+            self.is_dragging_key = false;
+            if self.selected_key_idx.is_some() {
+                self.r_slider.mouse_input(button, state, px, py_event, ui);
+                self.g_slider.mouse_input(button, state, px, py_event, ui);
+                self.b_slider.mouse_input(button, state, px, py_event, ui);
+                if self.del_button.mouse_input(button, state, px, py_event, ui) {
+                    if self.del_button.take_click() {
+                        if let Some(idx) = self.selected_key_idx {
+                            if self.keys.len() > 2 {
+                                self.keys.remove(idx);
+                                self.selected_key_idx = None;
+                                self.just_changed = true;
+                                self.arrange_fields();
+                            }
+                        }
+                    }
+                }
+                return true;
             }
-            list
         }
-    }
-    fn focus(&mut self) {
-        self.base.focused = true;
-        crate::widget::focus::set_focused(&mut self.preset_dropdown);
-    }
-    
-    fn unfocus(&mut self) {
-        self.base.focused = false;
-        self.preset_dropdown.unfocus();
-        self.line_type_dropdown.unfocus();
-        self.val_slider.unfocus();
-        self.del_button.unfocus();
-    }
+        false
     
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        if event.state != ElementState::Pressed { return false; }
+            }
+            Event::PointerMove { x, y, .. } => {
+                let (px, py_event) = (*x, *y);
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
+        let mut changed = false;
+        let track_x = self.base.x + 10.0;
+        let track_w = self.base.w - 20.0;
         
-        if event.logical_key == Key::Named(NamedKey::Tab) {
-            let is_shift = event.shift;
-            let self_ptr = self as *mut Self;
-            let mut children = unsafe {
-                let mut list = vec![
-                    (*self_ptr).preset_dropdown.as_ptr_mut(),
-                    (*self_ptr).line_type_dropdown.as_ptr_mut(),
-                ];
-                if (*self_ptr).selected_key_idx.is_some() {
-                    list.push((*self_ptr).val_slider.as_ptr_mut());
-                    list.push((*self_ptr).del_button.as_ptr_mut());
-                }
-                list
-            };
-            
-            let mut focused_idx = None;
-            for (idx, child) in children.iter().enumerate() {
-                if unsafe { ctx.is_focused(&**child) } {
-                    focused_idx = Some(idx);
-                    break;
+        if self.is_dragging_key {
+            if let Some(idx) = self.selected_key_idx {
+                let t = ((px - track_x) / track_w).clamp(0.0, 1.0);
+                self.keys[idx].pos = t;
+                self.sort_keys();
+                changed = true;
+            }
+        }
+        
+        if self.selected_key_idx.is_some() {
+            if self.r_slider.cursor_moved(px, py_event, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].color[0] = self.r_slider.inner().value();
+                    changed = true;
                 }
             }
-            
-            if let Some(curr) = focused_idx {
-                let next_idx = if is_shift {
-                    if curr == 0 { children.len() - 1 } else { curr - 1 }
-                } else {
-                    (curr + 1) % children.len()
-                };
-                unsafe {
-                    ctx.set_focused(&mut *children[next_idx]);
+            if self.g_slider.cursor_moved(px, py_event, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].color[1] = self.g_slider.inner().value();
+                    changed = true;
                 }
-            } else {
-                unsafe {
-                    ctx.set_focused(&mut *children[0]);
+            }
+            if self.b_slider.cursor_moved(px, py_event, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].color[2] = self.b_slider.inner().value();
+                    changed = true;
                 }
             }
-            return true;
+            if self.del_button.cursor_moved(px, py_event, ui) {
+                changed = true;
+            }
+        }
+        if changed {
+            self.just_changed = true;
+        }
+        changed
+    
+            }
+            Event::KeyInput(event) => {
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
+        if ui.is_focused(&self.r_slider) {
+            return self.r_slider.keyboard_input(event, ui);
+        }
+        if ui.is_focused(&self.g_slider) {
+            return self.g_slider.keyboard_input(event, ui);
+        }
+        if ui.is_focused(&self.b_slider) {
+            return self.b_slider.keyboard_input(event, ui);
         }
+        if ui.is_focused(&self.del_button) {
+            return self.del_button.keyboard_input(event, ui);
+        }
+        false
+    
+            }
+            _ => false,
+        }
+    }
+}
+
+impl Ramp {
+    fn arrange_fields(&mut self) {
+        let (x, y, w, h) = (self.base.x, self.base.y, self.base.w, self.base.h);
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
         
-        if ctx.is_focused(&self.preset_dropdown) {
-            return self.preset_dropdown.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.line_type_dropdown) {
-            return self.line_type_dropdown.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.val_slider) {
-            return self.val_slider.keyboard_input(event, ctx);
-        }
-        if ctx.is_focused(&self.del_button) {
-            return self.del_button.keyboard_input(event, ctx);
+        
+        let gh = (h - 70.0).max(30.0);
+        let sy = y + gh + 15.0;
+        
+        let track_x = x + 10.0;
+        let track_w = w - 20.0;
+        
+        if self.selected_key_idx.is_some() {
+            let gap = 10.0;
+            let del_w = 70.0;
+            let available_for_inputs = track_w - del_w - 3.0 * gap;
+            let col_w = (available_for_inputs / 3.0).max(40.0);
+            
+            self.preset_dropdown.set_rect(track_x, sy + 20.0, col_w, 20.0);
+            self.line_type_dropdown.set_rect(track_x + col_w + gap, sy + 20.0, col_w, 20.0);
+            self.val_slider.set_rect(track_x + 2.0 * (col_w + gap), sy + 20.0, col_w, 20.0);
+            self.del_button.set_rect(track_x + track_w - del_w, sy + 12.0, del_w, 28.0);
+        } else {
+            let gap = 10.0;
+            let col_w = (track_w - gap) / 2.0;
+            self.preset_dropdown.set_rect(track_x, sy + 20.0, col_w, 20.0);
+            self.line_type_dropdown.set_rect(track_x + col_w + gap, sy + 20.0, col_w, 20.0);
+            self.val_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+            self.del_button.set_rect(-1000.0, -1000.0, 0.0, 0.0);
         }
-        false
-    }
     
-    fn color(&self) -> [f32; 4] {
-        [0.15, 0.15, 0.18, 1.0]
     }
-    
-    fn preferred_height(&self) -> Option<f32> {
-        Some(150.0)
+}
+
+impl Layout for Ramp {
+    fn intrinsic_size(&self) -> Option<Size> {
+        Some(Size::new(0.0, 150.0))
     }
-    
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+
+    fn rect_assigned(&mut self, rect: Rect) {
+        let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
         self.base.x = x;
         self.base.y = y;
         self.base.w = w;
         self.base.h = h;
         
-        let self_ptr = self.as_ptr_mut();
-        let mut dummy = crate::context::UiContext::new();
-        self.val_slider.set_parent(Some(self_ptr), &mut dummy);
-        self.del_button.set_parent(Some(self_ptr), &mut dummy);
-        self.preset_dropdown.set_parent(Some(self_ptr), &mut dummy);
-        self.line_type_dropdown.set_parent(Some(self_ptr), &mut dummy);
         
         let gh = (h - 70.0).max(30.0);
         let sy = y + gh + 15.0;
@@ -734,9 +760,71 @@ impl Element for Ramp {
             self.val_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
             self.del_button.set_rect(-1000.0, -1000.0, 0.0, 0.0);
         }
+    
+    }
+
+    fn register_embedded_children(&mut self, host_id: WidgetId, ctx: &mut UiContext) {
+        let p = self.preset_dropdown.as_ptr_mut();
+        let id = self.preset_dropdown.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.line_type_dropdown.as_ptr_mut();
+        let id = self.line_type_dropdown.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.val_slider.as_ptr_mut();
+        let id = self.val_slider.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+        let p = self.del_button.as_ptr_mut();
+        let id = self.del_button.base().unwrap().id();
+        ctx.register_widget(id, p);
+        ctx.link_ids(host_id, id);
+    }
+}
+
+impl Paint for Ramp {
+    fn color(&self) -> [f32; 4] {
+        [0.15, 0.15, 0.18, 1.0]
+    }
+
+    fn popover(&self, _rect: Rect) -> Option<(f32, f32, f32, f32)> {
+        self.preset_dropdown.popover_rect()
+            .or_else(|| self.line_type_dropdown.popover_rect())
+    
     }
+
+    fn draw_popover(&self, _rect: Rect, pc: &mut dyn crate::layout::RenderTarget) {
+        self.preset_dropdown.render_popover(pc);
+        self.line_type_dropdown.render_popover(pc);
     
-    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+    }
+
+    // Field children are ctx-linked for event propagation but painted here — the walk
+    // must not also descend (the legacy own-labels rule, now with the children too).
+    fn paints_own_subtree(&self) -> bool {
+        true
+    }
+
+    fn paint(&self, _rect: Rect, pc: &mut PaintCtx) {
+        // Rounded container border + background (the legacy all_rounded_quads head).
+        let border_color = colors::ramp_border_color();
+        let bg_color = [0.15, 0.15, 0.18, 1.0];
+        let radius = 6.0f32;
+        pc.rounded_rect(
+            Rect { x: self.base.x - 1.0, y: self.base.y - 1.0, width: self.base.w + 2.0, height: self.base.h + 2.0 },
+            radius,
+            (true, true, true, true),
+            border_color,
+        );
+        pc.rounded_rect(
+            Rect { x: self.base.x, y: self.base.y, width: self.base.w, height: self.base.h },
+            (radius - 1.0).max(0.0),
+            (true, true, true, true),
+            bg_color,
+        );
+
+        let quads: Vec<(f32, f32, f32, f32, [f32; 4])> = {
         let mut quads = Vec::new();
         let gh = (self.base.h - 70.0).max(30.0);
         let track_x = self.base.x + 10.0;
@@ -769,43 +857,13 @@ impl Element for Ramp {
             quads.push((sx1, outline_y, slice_w, outline_h, [0.5, 0.75, 1.0, 1.0]));
         }
         
-        let ctx_dummy = crate::context::UiContext::new();
-        quads.extend(self.preset_dropdown.all_quads(&ctx_dummy));
-        quads.extend(self.line_type_dropdown.all_quads(&ctx_dummy));
-        
-        if self.selected_key_idx.is_some() {
-            quads.extend(self.val_slider.all_quads(&ctx_dummy));
-            quads.extend(self.del_button.all_quads(&ctx_dummy));
-        }
-        
-        quads
-    }
-
-    fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
-        if !self.visible() {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        
-        let border_color = colors::ramp_border_color();
-        let bg_color = [0.15, 0.15, 0.18, 1.0];
-        let radius = 6.0f32;
-        
-        // Draw container border (rounded quad) using full widget bounds
-        quads.push((self.base.x - 1.0, self.base.y - 1.0, self.base.w + 2.0, self.base.h + 2.0, radius, border_color, (true, true, true, true)));
-        // Draw container background (rounded quad) using full widget bounds
-        quads.push((self.base.x, self.base.y, self.base.w, self.base.h, (radius - 1.0).max(0.0), bg_color, (true, true, true, true)));
-        
-        // Extend children's rounded quads
-        for &child_ptr in &self.children(ctx) {
-            let widget = unsafe { &*child_ptr };
-            quads.extend(widget.all_rounded_quads(ctx));
-        }
-        
         quads
-    }
     
-    fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> {
+        };
+        for (qx, qy, qw, qh, qc) in quads {
+            pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
+        }
+        let circles: Vec<(f32, f32, f32, [f32; 4])> = {
         let mut circles = Vec::new();
         let gh = (self.base.h - 70.0).max(30.0);
         let track_x = self.base.x + 10.0;
@@ -823,12 +881,94 @@ impl Element for Ramp {
         }
         
         circles
+    
+        };
+        for (cx, cy, r, c) in circles {
+            pc.circle(cx, cy, r, c);
+        }
+        for (tl, font) in self.own_control_labels() {
+            pc.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, None);
+        }
+        let dummy = UiContext::new();
+        self.preset_dropdown.paint_self(&dummy, pc);
+        self.line_type_dropdown.paint_self(&dummy, pc);
+        if self.selected_key_idx.is_some() {
+            self.val_slider.paint_self(&dummy, pc);
+            self.del_button.paint_self(&dummy, pc);
+        }
+    }
+}
+
+impl Input for Ramp {
+    fn wants_tick(&self) -> bool {
+        true
+    }
+
+    /// The open dropdown popover extends the hit area (the 5p Dropdown pattern).
+    fn hit(&self, rect: Rect, x: f32, y: f32) -> bool {
+        if let Some((px, py, pw, ph)) = {
+        self.preset_dropdown.popover_rect()
+            .or_else(|| self.line_type_dropdown.popover_rect())
+    
+        } {
+            if x >= px && x <= px + pw && y >= py && y <= py + ph {
+                return true;
+            }
+        }
+        x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height
     }
+
+    fn tick_ctx(&mut self, dt: f32, ectx: &mut EventCtx) -> bool {
+        // Keep the field widgets' tracked parent pointing at the adapter (the dropdowns'
+        // label fade blends against the parent's color; legacy set_rect re-parented them).
+        if let Some(host) = ectx.host_ptr() {
+            let mut dummy = crate::context::UiContext::new();
+            self.preset_dropdown.set_parent(Some(host), &mut dummy);
+            self.line_type_dropdown.set_parent(Some(host), &mut dummy);
+            self.val_slider.set_parent(Some(host), &mut dummy);
+            self.del_button.set_parent(Some(host), &mut dummy);
+        }
+        let Some(ui) = ectx.ui.as_deref_mut() else {
+            return false;
+        };
+        let mut changed = self.just_changed;
+        self.just_changed = false;
+        
+        if self.preset_dropdown.tick(dt, ui) {
+            let idx = self.preset_dropdown.selected;
+            self.apply_preset(idx);
+            changed = true;
+        }
+        
+        if self.line_type_dropdown.tick(dt, ui) {
+            changed = true;
+        }
+        
+        if self.selected_key_idx.is_some() {
+            if self.val_slider.tick(dt, ui) {
+                if let Some(idx) = self.selected_key_idx {
+                    self.keys[idx].value = self.val_slider.inner().value();
+                    self.preset_dropdown.selected = 0; // Custom
+                }
+                changed = true;
+            }
+            if self.del_button.tick(dt, ui) {
+                self.preset_dropdown.selected = 0; // Custom
+                changed = true;
+            }
+        }
+        changed
     
-    fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py_event: f32, ctx: &mut UiContext) -> bool {
+    }
+
+    fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
+        match event {
+            Event::MouseButton { button, state, x, y, .. } => {
+                let (button, state, px, py_event) = (*button, *state, *x, *y);
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
         if button != MouseButton::Left { return false; }
         
-        if self.preset_dropdown.mouse_input(button, state, px, py_event, ctx) {
+        if self.preset_dropdown.mouse_input(button, state, px, py_event, ui) {
             if self.preset_dropdown.take_change() {
                 let idx = self.preset_dropdown.selected;
                 self.apply_preset(idx);
@@ -836,7 +976,7 @@ impl Element for Ramp {
             return true;
         }
         
-        if self.line_type_dropdown.mouse_input(button, state, px, py_event, ctx) {
+        if self.line_type_dropdown.mouse_input(button, state, px, py_event, ui) {
             return true;
         }
         
@@ -854,7 +994,7 @@ impl Element for Ramp {
                     self.selected_key_idx = Some(idx);
                     self.is_dragging_key = true;
                     self.val_slider.set_value(key.value);
-                    self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
+                    self.arrange_fields();
                     return true;
                 }
             }
@@ -872,13 +1012,13 @@ impl Element for Ramp {
                     self.selected_key_idx = Some(new_idx);
                     self.val_slider.set_value(val);
                 }
-                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
+                self.arrange_fields();
                 return true;
             }
             
             if self.selected_key_idx.is_some() {
-                if self.val_slider.mouse_input(button, state, px, py_event, ctx) { return true; }
-                if self.del_button.mouse_input(button, state, px, py_event, ctx) {
+                if self.val_slider.mouse_input(button, state, px, py_event, ui) { return true; }
+                if self.del_button.mouse_input(button, state, px, py_event, ui) {
                     if self.del_button.take_click() {
                         if let Some(idx) = self.selected_key_idx {
                             if self.keys.len() > 2 {
@@ -886,7 +1026,7 @@ impl Element for Ramp {
                                 self.selected_key_idx = None;
                                 self.preset_dropdown.selected = 0; // Custom
                                 self.just_changed = true;
-                                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
+                                self.arrange_fields();
                             }
                         }
                     }
@@ -896,8 +1036,8 @@ impl Element for Ramp {
         } else {
             self.is_dragging_key = false;
             if self.selected_key_idx.is_some() {
-                self.val_slider.mouse_input(button, state, px, py_event, ctx);
-                if self.del_button.mouse_input(button, state, px, py_event, ctx) {
+                self.val_slider.mouse_input(button, state, px, py_event, ui);
+                if self.del_button.mouse_input(button, state, px, py_event, ui) {
                     if self.del_button.take_click() {
                         if let Some(idx) = self.selected_key_idx {
                             if self.keys.len() > 2 {
@@ -905,7 +1045,7 @@ impl Element for Ramp {
                                 self.selected_key_idx = None;
                                 self.preset_dropdown.selected = 0; // Custom
                                 self.just_changed = true;
-                                self.set_rect(self.base.x, self.base.y, self.base.w, self.base.h);
+                                self.arrange_fields();
                             }
                         }
                     }
@@ -914,13 +1054,15 @@ impl Element for Ramp {
             }
         }
         false
-    }
     
-    fn cursor_moved(&mut self, px: f32, py_event: f32, ctx: &mut UiContext) -> bool {
-        if self.preset_dropdown.cursor_moved(px, py_event, ctx) {
+            }
+            Event::PointerMove { x, y, .. } => {
+                let (px, py_event) = (*x, *y);
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
+        if self.preset_dropdown.cursor_moved(px, py_event, ui) {
             return true;
         }
-        if self.line_type_dropdown.cursor_moved(px, py_event, ctx) {
+        if self.line_type_dropdown.cursor_moved(px, py_event, ui) {
             return true;
         }
         
@@ -943,14 +1085,14 @@ impl Element for Ramp {
         }
         
         if self.selected_key_idx.is_some() {
-            if self.val_slider.cursor_moved(px, py_event, ctx) {
+            if self.val_slider.cursor_moved(px, py_event, ui) {
                 if let Some(idx) = self.selected_key_idx {
                     self.keys[idx].value = self.val_slider.inner().value();
                     self.preset_dropdown.selected = 0; // Custom
                     changed = true;
                 }
             }
-            if self.del_button.cursor_moved(px, py_event, ctx) {
+            if self.del_button.cursor_moved(px, py_event, ui) {
                 changed = true;
             }
         }
@@ -958,120 +1100,83 @@ impl Element for Ramp {
             self.just_changed = true;
         }
         changed
-    }
-    
-    fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> {
-        self.preset_dropdown.popover_rect()
-            .or_else(|| self.line_type_dropdown.popover_rect())
-    }
-    
-    fn render_popover(&self, pc: &mut dyn crate::layout::RenderTarget) {
-        self.preset_dropdown.render_popover(pc);
-        self.line_type_dropdown.render_popover(pc);
-    }
     
-    /// The walk drops a container's own text (assumed child aggregate) — Ramp's control
-    /// labels are its OWN; the children (dropdowns/slider/button) are painted by the
-    /// walk's descent, so only the own labels are emitted here, over the default's
-    /// container geometry.
-    fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
-        use crate::scene::layout::Rect;
-        let (x, y, w, h) = self.rect();
-        let rect = Rect { x, y, width: w, height: h };
-        let color = self.color();
-        let cr = self.corner_radii();
-        let radii = (cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left);
-        if let Some(depth) = self.plate_bevel() {
-            ctx.bevel(rect, radii, color, depth);
-        } else if let Some((border_color, thickness)) = self.solid_border() {
-            ctx.border(rect, radii, color, border_color, thickness);
-        } else if color[3].abs() > 0.001 {
-            let (r1, r2, r3, r4) = self.rounded_corners();
-            if r1 || r2 || r3 || r4 {
-                ctx.rounded_rect(rect, self.corner_radius(), (r1, r2, r3, r4), color);
             }
+            Event::KeyInput(event) => {
+                let Some(ui) = ectx.ui.as_deref_mut() else { return false; };
+        if event.state != ElementState::Pressed { return false; }
+        
+        if event.logical_key == Key::Named(NamedKey::Tab) {
+            let is_shift = event.shift;
+            let self_ptr = self as *mut Self;
+            let mut children = unsafe {
+                let mut list = vec![
+                    (*self_ptr).preset_dropdown.as_ptr_mut(),
+                    (*self_ptr).line_type_dropdown.as_ptr_mut(),
+                ];
+                if (*self_ptr).selected_key_idx.is_some() {
+                    list.push((*self_ptr).val_slider.as_ptr_mut());
+                    list.push((*self_ptr).del_button.as_ptr_mut());
+                }
+                list
+            };
+            
+            let mut focused_idx = None;
+            for (idx, child) in children.iter().enumerate() {
+                if unsafe { ui.is_focused(&**child) } {
+                    focused_idx = Some(idx);
+                    break;
+                }
+            }
+            
+            if let Some(curr) = focused_idx {
+                let next_idx = if is_shift {
+                    if curr == 0 { children.len() - 1 } else { curr - 1 }
+                } else {
+                    (curr + 1) % children.len()
+                };
+                unsafe {
+                    ui.set_focused(&mut *children[next_idx]);
+                }
+            } else {
+                unsafe {
+                    ui.set_focused(&mut *children[0]);
+                }
+            }
+            return true;
         }
-        for (qx, qy, qw, qh, c) in self.all_quads(ui) {
-            ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, c);
+        
+        if ui.is_focused(&self.preset_dropdown) {
+            return self.preset_dropdown.keyboard_input(event, ui);
         }
-        for (cx, cy, r, t, start, end, c) in self.extra_arcs() {
-            ctx.arc(cx, cy, r, t, start, end, c);
+        if ui.is_focused(&self.line_type_dropdown) {
+            return self.line_type_dropdown.keyboard_input(event, ui);
         }
-        for (cx, cy, r, c) in self.extra_circles() {
-            ctx.circle(cx, cy, r, c);
+        if ui.is_focused(&self.val_slider) {
+            return self.val_slider.keyboard_input(event, ui);
         }
-        for (tl, font) in self.own_control_labels() {
-            ctx.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font, None);
+        if ui.is_focused(&self.del_button) {
+            return self.del_button.keyboard_input(event, ui);
         }
-    }
+        false
     
-}
-
-impl Ramp {
-    /// The ramp's OWN control labels (Preset / Line Type / Value-when-selected), each
-    /// with its control's font — shared by the legacy fonted getter and `paint_self`.
-    fn own_control_labels(&self) -> Vec<(TextLabel, Option<String>)> {
-        let mut labels = Vec::new();
-
-        let track_x = self.base.x + 10.0;
-        let track_w = self.base.w - 20.0;
-        let h = self.base.h;
-        let gh = (h - 70.0).max(30.0);
-        let sy = self.base.y + gh + 15.0;
-
-        let gap = 10.0;
-        let col_w = if self.selected_key_idx.is_some() {
-            let del_w = 70.0;
-            let available_for_inputs = track_w - del_w - 3.0 * gap;
-            (available_for_inputs / 3.0).max(40.0)
-        } else {
-            (track_w - gap) / 2.0
-        };
-
-        let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-        let label_color = colors::control_label_color_detached_u8();
-
-        labels.push((
-            TextLabel {
-                text: "Preset".to_string(),
-                x: track_x + 4.0,
-                y: sy - 2.0,
-                font_size,
-                color: label_color,
-            },
-            self.preset_dropdown.widget_font(),
-        ));
-
-        labels.push((
-            TextLabel {
-                text: "Line Type".to_string(),
-                x: track_x + col_w + gap + 4.0,
-                y: sy - 2.0,
-                font_size,
-                color: label_color,
-            },
-            self.line_type_dropdown.widget_font(),
-        ));
-
-        if self.selected_key_idx.is_some() {
-            labels.push((
-                TextLabel {
-                    text: "Value".to_string(),
-                    x: track_x + 2.0 * (col_w + gap) + 4.0,
-                    y: sy - 2.0,
-                    font_size,
-                    color: label_color,
-                },
-                self.val_slider.widget_font(),
-            ));
+            }
+            Event::FocusIn => {
+                if let Some(ui) = ectx.ui.as_deref_mut() {
+                    ui.set_focused(&mut self.preset_dropdown);
+                }
+                false
+            }
+            Event::FocusOut => {
+        self.base.focused = false;
+        self.preset_dropdown.unfocus();
+        self.line_type_dropdown.unfocus();
+        self.val_slider.unfocus();
+        self.del_button.unfocus();
+    
+                false
+            }
+            _ => false,
         }
-
-        labels
-    }
-}
-
-impl Drop for Ramp {
-    fn drop(&mut self) {
-        clear_widget_references(self);
     }
 }
diff --git a/src/widget/model.rs b/src/widget/model.rs
index e9b91ef..e5b4f37 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -1265,8 +1265,16 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
     fn paint_self(&self, ui: &UiContext, ctx: &mut PaintCtx) {
         let mut tmp = PaintCtx::new();
         Paint::paint(&self.inner, self.content_rect(), &mut tmp);
+        // Subtree painters (paints_own_subtree) author their COMPLETE text in paint() —
+        // per-child fonts and clip bounds included — so their Text prims pass through
+        // verbatim and the single-font own-labels re-derivation below is skipped
+        // (re-deriving would flatten a composite's mixed child fonts to widget_font).
+        let subtree = Paint::paints_own_subtree(&self.inner);
         for item in tmp.finish().items {
             match item.prim {
+                Prim::Text { text, x, y, font_size, color, font, bounds, .. } if subtree => {
+                    ctx.text_with(text, x, y, font_size, color, font, bounds)
+                }
                 Prim::Text { .. } => {}
                 Prim::Quad { rect, color } => ctx.quad(rect, color),
                 Prim::RoundedRect { rect, radius, corners, color } => ctx.rounded_rect(rect, radius, corners, color),
@@ -1291,6 +1299,9 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
         // (caveat: its contract includes raw container children — those few widgets keep the
         // hatch until their hosts adopt the walk), else the standard own-labels bridge (prim
         // text + the detached base label, one font, text_bounds or the scroll-ancestor clip).
+        if subtree {
+            return;
+        }
         let labels = if Paint::serves_legacy_labels(&self.inner) {
             Paint::legacy_labels_with_font_and_bounds(&self.inner, self.content_rect(), ui)
         } else {