GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: labeled dropdowns expand their inset upward — label rides the slope
A labeled dropdown's relief used to be the symmetric flush-inset ring
around the trigger, with the detached label floating on plain plate above
it. The inset now expands upward over the label strip: the ring's top wall
widens into a long descending slope whose inward half spans the whole
strip — the label sits ON the slope — ending exactly at the trigger's top
edge where the lip's own bevel rises back up; the side walls run up
alongside the strip to meet it. Unlabeled (and side-label) dropdowns keep
the symmetric inset_plate.
The ramp's Preset/Line dropdowns become real labeled dropdowns (rects
include the strip; the ramp no longer paints those labels itself), so the
strip picks the style up in the popup, the params rows, and any scene-path
host. The Value label moves onto the same label line.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/input/dropdown.rs | 27 ++++++++++++++++
src/widget/input/ramp.rs | 76 ++++++++++++++++++++++++--------------------
2 files changed, 68 insertions(+), 35 deletions(-)
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 67d4a3b..a940ae9 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -326,6 +326,33 @@ impl Dropdown {
// Flush inset plate: groove ring down, beveled lip back up, face
// level with the surface (transparent raw fill = edges only).
let face = if raw_bg[3] > 0.001 { bg_color } else { [0.0; 4] };
+ let strip = self.label_top();
+ if strip > 0.0 {
+ // Labeled: the inset expands upward over the label strip. The
+ // ring's top wall widens into a long descending slope whose
+ // inward half spans the whole strip — the label sits ON the
+ // slope — ending exactly at the trigger's top edge, where the
+ // lip's own bevel rises back up. The side walls run up
+ // alongside the strip to meet it.
+ let g = depth * 0.5;
+ let outer = Rect {
+ x: x - g,
+ y: y - strip - g,
+ width: w + 2.0 * g,
+ height: visual_h + strip + 2.0 * g,
+ };
+ let orad = (r4[0] + g, r4[1] + g, r4[2] + g, r4[3] + g);
+ ctx.recess_edges(outer, orad, depth, (false, true, true, true));
+ ctx.recess_edges(outer, orad, 2.0 * (strip + g), (true, false, false, false));
+ let rect = Rect { x, y, width: w, height: visual_h };
+ let rrad = (r4[0], r4[1], r4[2], r4[3]);
+ if face[3] > 0.001 {
+ ctx.bevel(rect, rrad, face, depth);
+ } else {
+ ctx.boss(rect, rrad, depth);
+ }
+ return;
+ }
ctx.inset_plate(
Rect { x, y, width: w, height: visual_h },
(r4[0], r4[1], r4[2], r4[3]),
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index 4bce152..ce40363 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -144,6 +144,8 @@ impl Ramp {
};
// Short names on purpose: the strip's columns are narrow, and these
// render inside param rows too ("Bevel (Raised)" used to clip).
+ // Labeled: the dropdowns draw their own detached labels, sitting on
+ // the expanded top wall of their inset (the labeled-relief style).
let preset_dropdown = Dropdown::new(
vec![
"Custom".to_string(),
@@ -154,14 +156,14 @@ impl Ramp {
"Valley".to_string(),
],
2,
- ).with_open_upward(true);
+ ).with_open_upward(true).with_label("Preset");
let line_type_dropdown = Dropdown::new(
vec![
"Linear".to_string(),
"Bezier".to_string(),
],
0,
- ).with_open_upward(true);
+ ).with_open_upward(true).with_label("Line");
Adapted::new(Ramp {
base: Widget::new(),
@@ -331,37 +333,27 @@ pub fn parse_ramp_spec(spec: &str) -> Option<(Vec<(f32, f32)>, bool)> {
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`.
- /// Positions derive from the arranged column rects (`arrange_fields` is the one
- /// source of the strip's geometry), on the label line 8px under the graph.
+ /// The ramp's OWN control labels — just Value-when-selected now: the
+ /// dropdowns carry real labels and draw them themselves (on the expanded
+ /// top wall of their inset). The Value label rides the same label line
+ /// (the dropdown rects' top).
fn own_control_labels(&self) -> Vec<(TextLabel, Option<String>)> {
- let gh = (self.base.h - 70.0).max(30.0);
- let label_y = self.base.y + 10.0 + gh + 8.0;
- let (_, font_size) = crate::layout::control_label_font_detached_parsed();
- let label_color = colors::control_label_color_detached_u8();
-
- let label = |x: f32, text: &str, font: Option<String>| {
- (
+ let mut labels = Vec::new();
+ if self.selected_key_idx.is_some() {
+ let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+ let label_color = colors::control_label_color_detached_u8();
+ let (val_x, _, _, _) = self.val_slider.rect();
+ let (_, dd_y, _, _) = self.preset_dropdown.rect();
+ labels.push((
TextLabel {
- text: text.to_string(),
- x: x + 4.0,
- y: label_y,
+ text: "Value".to_string(),
+ x: val_x + 4.0,
+ y: dd_y,
font_size,
color: label_color,
},
- font,
- )
- };
-
- let mut labels = Vec::new();
- let (pre_x, _, _, _) = self.preset_dropdown.rect();
- labels.push(label(pre_x, "Preset", self.preset_dropdown.widget_font()));
- let (line_x, _, _, _) = self.line_type_dropdown.rect();
- labels.push(label(line_x, "Line", self.line_type_dropdown.widget_font()));
- if self.selected_key_idx.is_some() {
- let (val_x, _, _, _) = self.val_slider.rect();
- labels.push(label(val_x, "Value", self.val_slider.widget_font()));
+ self.val_slider.widget_font(),
+ ));
}
labels
}
@@ -793,17 +785,31 @@ impl Input for ColorRamp {
}
impl Ramp {
+ /// The detached-label strip height the labeled dropdowns carry
+ /// (`Widget::label_offset`'s formula).
+ fn label_strip() -> f32 {
+ if crate::layout::control_label_layout() == "side" {
+ return 0.0;
+ }
+ let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+ font_size + crate::layout::control_label_margin()
+ }
+
/// Lay out the control strip under the curve area. One rhythm: the label
/// line sits 8px under the graph, the controls 4px under the labels, all
- /// columns one shared height on one shared baseline. The preset column
- /// takes the wider share — its options ("Bevel (Raised)") are the longest
- /// strings in the strip and used to clip.
+ /// columns one shared height on one shared baseline. The labeled dropdowns
+ /// get rects that INCLUDE their label strip (the adapter carves it off the
+ /// content); the unlabeled columns get the content band only. The preset
+ /// column takes the wider share — its options are the strip's longest
+ /// strings and used to clip.
fn arrange_fields(&mut self) {
let (x, y, w, h) = (self.base.x, self.base.y, self.base.w, self.base.h);
let gh = (h - 70.0).max(30.0);
let graph_bottom = y + 10.0 + gh;
let ctrl_y = graph_bottom + 28.0;
let ctrl_h = 22.0;
+ let strip = Self::label_strip();
+ let (dd_y, dd_h) = (ctrl_y - strip, ctrl_h + strip);
let track_x = x + 10.0;
let track_w = w - 20.0;
let gap = 10.0;
@@ -816,16 +822,16 @@ impl Ramp {
let pre_w = (avail * 0.40).max(40.0);
let line_w = (avail * 0.32).max(40.0);
let val_w = (avail - pre_w - line_w).max(40.0);
- self.preset_dropdown.set_rect(track_x, ctrl_y, pre_w, ctrl_h);
- self.line_type_dropdown.set_rect(track_x + pre_w + gap, ctrl_y, line_w, ctrl_h);
+ self.preset_dropdown.set_rect(track_x, dd_y, pre_w, dd_h);
+ self.line_type_dropdown.set_rect(track_x + pre_w + gap, dd_y, line_w, dd_h);
self.val_slider.set_rect(track_x + pre_w + line_w + 2.0 * gap, ctrl_y, val_w, ctrl_h);
self.del_button.set_rect(track_x + track_w - del_w, ctrl_y, del_w, ctrl_h);
} else {
// Two columns, preset the wider share.
let pre_w = ((track_w - gap) * 0.58).max(40.0);
let line_w = (track_w - gap - pre_w).max(40.0);
- self.preset_dropdown.set_rect(track_x, ctrl_y, pre_w, ctrl_h);
- self.line_type_dropdown.set_rect(track_x + pre_w + gap, ctrl_y, line_w, ctrl_h);
+ self.preset_dropdown.set_rect(track_x, dd_y, pre_w, dd_h);
+ self.line_type_dropdown.set_rect(track_x + pre_w + gap, dd_y, line_w, dd_h);
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);
}