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

commitcef29e9ca438db7def1584f359fdda82d25f5ba2
parenta8574ae107
authorLucas Galante <[email protected]>
date2026-07-04 22:04
Add Line Type dropdown (Linear/Bezier) to Ramp element with cubic Hermite spline interpolation

 src/widget/input/ramp.rs | 42 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 4 deletions(-)

diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index bc78f64..4d335d4 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -419,6 +419,7 @@ pub struct Ramp {
     pub val_slider: Slider,
     pub del_button: Button,
     pub preset_dropdown: Dropdown,
+    pub line_type_dropdown: Dropdown,
     
     pub parent: Option<*mut (dyn Element + 'static)>,
 }
@@ -446,6 +447,14 @@ impl Ramp {
             2,
         ).with_label("Preset")
          .with_open_upward(false);
+        let line_type_dropdown = Dropdown::new(
+            vec![
+                "Linear".to_string(),
+                "Bezier".to_string(),
+            ],
+            0,
+        ).with_label("Line Type")
+         .with_open_upward(false);
         
         Self {
             base: Widget::new(),
@@ -456,6 +465,7 @@ impl Ramp {
             val_slider,
             del_button,
             preset_dropdown,
+            line_type_dropdown,
             parent: None,
         }
     }
@@ -524,7 +534,12 @@ impl Ramp {
                     return k1.value;
                 }
                 let w = (t - k1.pos) / range;
-                return k1.value * (1.0 - w) + k2.value * w;
+                if self.line_type_dropdown.selected == 1 {
+                    let w_smooth = w * w * (3.0 - 2.0 * w);
+                    return k1.value * (1.0 - w_smooth) + k2.value * w_smooth;
+                } else {
+                    return k1.value * (1.0 - w) + k2.value * w;
+                }
             }
         }
         self.keys[0].value
@@ -571,6 +586,10 @@ impl Element for Ramp {
             changed = true;
         }
         
+        if self.line_type_dropdown.tick(dt, ctx) {
+            changed = true;
+        }
+        
         if self.selected_key_idx.is_some() {
             if self.val_slider.tick(dt, ctx) {
                 if let Some(idx) = self.selected_key_idx {
@@ -597,6 +616,7 @@ impl Element for Ramp {
         unsafe {
             let mut list = vec![
                 &mut (*self_ptr).preset_dropdown as *mut Dropdown as *mut (dyn Element + 'static),
+                &mut (*self_ptr).line_type_dropdown as *mut Dropdown as *mut (dyn Element + 'static),
             ];
             if self.selected_key_idx.is_some() {
                 list.push(&mut (*self_ptr).val_slider as *mut Slider as *mut (dyn Element + 'static));
@@ -621,15 +641,17 @@ impl Element for Ramp {
         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 = 80.0;
         let sy = y + gh + 25.0;
         
-        // Preset dropdown is always visible at the bottom row
-        self.preset_dropdown.set_rect(x + 10.0, sy, 140.0, 20.0);
+        // Dropdowns are always visible at the bottom row
+        self.preset_dropdown.set_rect(x + 10.0, sy, 110.0, 20.0);
+        self.line_type_dropdown.set_rect(x + 130.0, sy, 110.0, 20.0);
         
         if self.selected_key_idx.is_some() {
-            self.val_slider.set_rect(x + 165.0, sy, 140.0, 20.0);
+            self.val_slider.set_rect(x + 250.0, sy, 110.0, 20.0);
             self.del_button.set_rect(x + w - 80.0, sy - 4.0, 70.0, 28.0);
         } else {
             self.val_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
@@ -688,6 +710,7 @@ impl Element for Ramp {
         
         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));
@@ -728,6 +751,10 @@ impl Element for Ramp {
             return true;
         }
         
+        if self.line_type_dropdown.mouse_input(button, state, px, py_event, ctx) {
+            return true;
+        }
+        
         let gh = 80.0;
         let track_x = self.base.x + 10.0;
         let track_w = self.base.w - 20.0;
@@ -808,6 +835,9 @@ impl Element for Ramp {
         if self.preset_dropdown.cursor_moved(px, py_event, ctx) {
             return true;
         }
+        if self.line_type_dropdown.cursor_moved(px, py_event, ctx) {
+            return true;
+        }
         
         let mut changed = false;
         let gh = 80.0;
@@ -847,15 +877,18 @@ impl Element for Ramp {
     
     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);
     }
     
     fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
         let mut labels = Vec::new();
         labels.extend(self.preset_dropdown.text_labels_with_font_and_bounds(ctx));
+        labels.extend(self.line_type_dropdown.text_labels_with_font_and_bounds(ctx));
         if self.selected_key_idx.is_some() {
             labels.extend(self.val_slider.text_labels_with_font_and_bounds(ctx));
             labels.extend(self.del_button.text_labels_with_font_and_bounds(ctx));
@@ -866,6 +899,7 @@ impl Element for Ramp {
     fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
         let mut items = Vec::new();
         items.extend(self.preset_dropdown.get_text_items());
+        items.extend(self.line_type_dropdown.get_text_items());
         if self.selected_key_idx.is_some() {
             items.extend(self.val_slider.get_text_items());
             items.extend(self.del_button.get_text_items());