GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: labeled sliders carve their label tab too — the section idiom
The recessed slider's label now sits in the same carve-out tab the labeled
Dropdown grew: a flat recessed well hugging the label run (top/right/left
walls at the track's own depth), bottom open into the track's well, the
well's top wall picking up right of the tab's throat. Slider gains the
Dropdown's 4px detached-label inset so the text clears the tab's left wall.
The ramp's value slider becomes a real labeled slider ("Value") and the
ramp stops hand-drawing any strip labels — every labeled control in the
strip now draws its own tab.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/input/ramp.rs | 36 ++++-------------------------
src/widget/input/slider.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 59 insertions(+), 33 deletions(-)
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index 445658d..02da506 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -133,7 +133,9 @@ impl Ramp {
RampKey { pos: 1.0, value: 0.5 },
];
- let val_slider = Slider::new();
+ // Labeled like the dropdowns: the slider draws "Value" in its own
+ // carve-out tab.
+ let val_slider = Slider::new().with_label("Value");
// A square x-icon button (cce-icons); label fallback if the icon set
// is missing on this machine.
let del_button = match crate::upload_icon("x", 32) {
@@ -332,33 +334,6 @@ pub fn parse_ramp_spec(spec: &str) -> Option<(Vec<(f32, f32)>, bool)> {
}
-impl Ramp {
- /// 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 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: "Value".to_string(),
- x: val_x + 4.0,
- y: dd_y,
- font_size,
- color: label_color,
- },
- self.val_slider.widget_font(),
- ));
- }
- labels
- }
-}
-
impl ColorRamp {
fn arrange_fields(&mut self) {
@@ -824,7 +799,7 @@ impl Ramp {
let val_w = (avail - pre_w - line_w).max(40.0);
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.val_slider.set_rect(track_x + pre_w + line_w + 2.0 * gap, dd_y, val_w, dd_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.
@@ -1008,9 +983,6 @@ impl Paint for Ramp {
}
let depth = crate::layout::bevel_width().min(graph.height * 0.2);
pc.recess(graph, radii, depth);
- 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);
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 4be0574..92f12ce 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -144,6 +144,21 @@ impl Slider {
Some((thumb_x + g.thumb_size / 2.0, thumb_y + g.thumb_size / 2.0, diameter / 2.0, color))
}
+ /// The detached-label strip height above the content rect — a replica of
+ /// `Widget::label_offset` over the synced label (zero in side layout or
+ /// unlabeled).
+ fn label_top(&self) -> f32 {
+ if crate::layout::control_label_layout() == "side" {
+ return 0.0;
+ }
+ if self.label.is_some() {
+ let (_, font_size) = crate::layout::control_label_font_detached_parsed();
+ font_size + crate::layout::control_label_margin()
+ } else {
+ 0.0
+ }
+ }
+
fn geom(&self, rect: Rect) -> SliderGeom {
let side = side_offset(&self.label);
let x = rect.x + side;
@@ -227,6 +242,12 @@ impl Layout for Slider {
false // legacy Slider::set_rect stored the assigned rect verbatim
}
+ /// Detached label x inset — keeps the label clear of its carve-out tab's
+ /// left wall (the Dropdown value).
+ fn detached_label_inset(&self) -> f32 {
+ 4.0
+ }
+
fn intrinsic_size(&self) -> Option<Size> {
Some(Size::new(0.0, crate::layout::slider_height()))
@@ -323,7 +344,40 @@ impl Paint for Slider {
// Carve AFTER the fill so the wall's shading modulates whatever it
// crosses — the same order TextBox uses for its edit fill.
if self.recessed {
- ctx.recess(track_rect, (radius, radius, radius, radius), recess_t);
+ let strip = self.label_top();
+ if strip > 0.0 {
+ // Labeled: the label sits in a CARVE-OUT tab, the section-
+ // title idiom (the labeled Dropdown's composition) — a flat
+ // recessed well hugging the label run, bottom open into the
+ // track's well; the well's top wall picks up right of the
+ // tab's throat.
+ let (fam, fsize) = crate::layout::control_label_font_detached_parsed();
+ let text_w = self
+ .label
+ .as_deref()
+ .map(|l| crate::widget::display::measure_text_width(l, &fam, fsize))
+ .unwrap_or(0.0);
+ let inset = 4.0; // Layout::detached_label_inset — the label's x offset
+ let tab_w = (text_w + 2.0 * inset).max(2.0 * radius + 8.0).min(g.track_w);
+ let tab_r = g.track_x + tab_w;
+ ctx.recess_edges(
+ Rect { x: g.track_x, y: g.y - strip, width: tab_w, height: strip },
+ (radius, radius.min(strip * 0.5), 0.0, 0.0),
+ recess_t,
+ (true, true, false, true),
+ );
+ ctx.recess_edges(track_rect, (0.0, 0.0, radius, radius), recess_t, (false, true, true, true));
+ if g.track_x + g.track_w - tab_r > 0.5 {
+ ctx.recess_edges(
+ Rect { x: tab_r, y: g.y, width: g.track_x + g.track_w - tab_r, height: g.h },
+ (0.0, radius, 0.0, 0.0),
+ recess_t,
+ (true, false, false, false),
+ );
+ }
+ } else {
+ ctx.recess(track_rect, (radius, radius, radius, radius), recess_t);
+ }
}
// Thumb. A real Circle prim, not a full-radius rounded rect: rounded