GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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 {