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

commit1ac832b14053dedf85fe5a70c8a1fcc7c61c522d
parent86e84473a7
authorLucas Galante <[email protected]>
date2026-07-23 15:05
refactor: labeled dropdown inset rises only over the label — a text-width tab

The full-width raised top wall becomes a label-hugging TAB (the section-
title-tab idiom): only where the label text runs does the inset extend
upward, its wide slope carrying the label down onto the trigger's lip; the
tab's right wall steps back down at a throat and the ring keeps its normal
top wall from there. Composed from edge-suppressed recess pieces so walls
continue across the joints (ring one prim for its three internally-blended
corners; tab sides; the wide slope with its midpoint boundary).

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/widget/input/dropdown.rs | 93 +++++++++++++++++++++++++++++++-------------
 1 file changed, 67 insertions(+), 26 deletions(-)

diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 38b0a9f..5020c4d 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -328,35 +328,76 @@ impl Dropdown {
             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.
+                // Labeled: the inset grows a label-width TAB (the section-
+                // title-tab idiom) — only where the label text runs does the
+                // top wall rise over the strip, widening into a long
+                // descending slope the label sits on (its inward half spans
+                // the whole strip, ending at the trigger's top edge where the
+                // lip's bevel rises back up). Right of the tab the ring keeps
+                // its normal top wall. Composed from edge-suppressed pieces,
+                // section-relief style, so walls continue across the joints.
                 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));
-                // The slope profile straddles its boundary by ±roll/2, so the
-                // top wall's boundary sits at the MIDPOINT of the run it
-                // should cover — from the inset's top edge down to the
-                // trigger's top — or the outward half would climb past the
-                // inset into whatever sits above (it washed into the ramp's
-                // graph; found the hard way).
+                let (outer_x, outer_r) = (x - g, x + w + g);
+                let (tab_top, ring_top) = (y - strip - g, y - g);
+                let bottom = y + visual_h + g;
+                // The tab hugs the label run (drawn at x + inset): text width
+                // plus the inset each side, kept inside the trigger's span.
+                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 + 2.0 * g)
+                    .max(2.0 * orad.0 + 8.0)
+                    .min(outer_r - outer_x);
+                let tab_r = outer_x + tab_w;
+
+                // The ring proper: right + bottom + left walls, one prim so
+                // its corners blend internally.
+                ctx.recess_edges(
+                    Rect { x: outer_x, y: ring_top, width: w + 2.0 * g, height: visual_h + 2.0 * g },
+                    (0.0, 0.0, orad.2, orad.3),
+                    depth,
+                    (false, true, true, true),
+                );
+                // Ring top wall, right of the tab (starts at the tab's throat).
+                if outer_r - tab_r > 0.5 {
+                    ctx.recess_edges(
+                        Rect { x: tab_r, y: ring_top, width: outer_r - tab_r, height: visual_h + 2.0 * g },
+                        (0.0, orad.1, 0.0, 0.0),
+                        depth,
+                        (true, false, false, false),
+                    );
+                }
+                // Tab side walls, continuing the ring's left wall up to the
+                // tab's top corner and closing its right side.
+                ctx.recess_edges(
+                    Rect { x: outer_x, y: tab_top, width: tab_w, height: strip },
+                    (orad.0, orad.1.min(strip * 0.5), 0.0, 0.0),
+                    depth,
+                    (false, true, false, true),
+                );
+                // The wide top slope, tab width only. The slope profile
+                // straddles its boundary by ±roll/2, so the boundary sits at
+                // the MIDPOINT of the run it covers — the tab's top edge down
+                // to the trigger's top — or the outward half would climb past
+                // the inset into whatever sits above (it washed into the
+                // ramp's graph; found the hard way).
                 let span = strip + g;
-                let top = Rect {
-                    x: outer.x,
-                    y: outer.y + span * 0.5,
-                    width: outer.width,
-                    height: outer.height - span * 0.5,
-                };
-                ctx.recess_edges(top, orad, span, (true, false, false, false));
+                ctx.recess_edges(
+                    Rect {
+                        x: outer_x,
+                        y: tab_top + span * 0.5,
+                        width: tab_w,
+                        height: bottom - (tab_top + span * 0.5),
+                    },
+                    (orad.0, orad.1.min(strip * 0.5), 0.0, 0.0),
+                    span,
+                    (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 {