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

commit9903e1261a5262f19ab0c67dfefe7806aae920e1
parent7cbd183996
authorLucas Galante <[email protected]>
date2026-08-28 14:58
fix: the textpick picker is a flush inset control, not a raised island

The picker button inside a textpick row's well swapped its boss for a
trough ring (picker_troughs, drawn after reliefs): face level with the
well floor, a valley seam straddling its outline — the dropdown
trigger's and breadcrumb run's inset language. The boss read as a
raised chip, which no other inset control in the DE does. reliefs()
keeps speaking only boss/recess; troughs travel through their own
getter.

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

 src/widget/container/parameters_bg.rs | 68 +++++++++++++++++++++++------------
 1 file changed, 46 insertions(+), 22 deletions(-)

diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 05eb4a4..2c7322e 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -606,9 +606,10 @@ impl ParametersBg {
                     // The picker button nests INSIDE the text box's recessed
                     // well (the box spans the full row): below the detached
                     // label band, inset from the well's walls by exactly the
-                    // well's carve depth, so the button's raised walls abut
-                    // the recess walls — together they read as the DE's
-                    // trough ring around an inset button (see the carve pass).
+                    // well's carve depth. Its relief is a flush trough ring
+                    // ([`Self::picker_troughs`]) whose valley straddles this
+                    // outline, so the outer half meets the well wall's roll
+                    // and the two shade into one inset seam.
                     let label_top = if crate::layout::control_label_layout() == "side" {
                         0.0
                     } else {
@@ -1161,25 +1162,12 @@ impl ParametersBg {
             }
             // (control, its configured corner radius, raised vs recessed)
             let ctl: Option<(&dyn WidgetHost, f32, bool)> = if is_text_row(&p.2) {
-                // A textpick row's picker button: a raised island inside the
-                // well. Its walls carry the SAME depth as the well's recess
-                // and abut it (the layout insets the button by that depth),
-                // so wall-against-wall the pair forms the trough valley
-                // around the button's top, right, and bottom — the DE's
-                // inset-button-in-a-trough look. The radius is the well
-                // radius's parallel curve at that inset.
-                if p.2.starts_with("textpick") {
-                    if let (Some(d), Some(tb)) = (&self.choices[i], &self.texts[i]) {
-                        let (bx, by, bw, bh) = d.rect();
-                        let (_, _, _, th) = tb.rect();
-                        let ty = crate::widget::label_offset(tb);
-                        if bw > 0.0 && bh > 0.0 {
-                            let depth = crate::layout::bevel_width().min((th - ty) * 0.2);
-                            let r = (crate::layout::textbox_corner_radius() - depth).max(2.0);
-                            out.push((bx, by, bw, bh, (r, r, r, r), depth, true, all));
-                        }
-                    }
-                }
+                // The textpick picker button is NOT in this list: it is a
+                // flush inset control (face level with the well floor, a
+                // valley seam around it — the dropdown trigger's trough
+                // language, not a boss), and troughs travel through
+                // [`Self::picker_troughs`]. A boss here read as a raised
+                // island, which no other inset control in the DE does.
                 self.texts[i].as_ref().map(|w| (w as &dyn WidgetHost, crate::layout::textbox_corner_radius(), false))
             } else if p.2.starts_with("choice") {
                 self.choices[i].as_ref().map(|w| (w as &dyn WidgetHost, crate::layout::dropdown_corner_radius(), true))
@@ -1244,6 +1232,39 @@ impl ParametersBg {
         out
     }
 
+    /// The textpick picker buttons' trough rings — `(x, y, w, h, radii, depth)`
+    /// for [`crate::scene::paint::PaintCtx::trough`], drawn by the host AFTER
+    /// [`Self::reliefs`]. The picker is a FLUSH inset control: its face stays
+    /// level with the well floor and a valley seam straddles its outline — the
+    /// dropdown trigger's (and the breadcrumb run's) inset language. It cannot
+    /// ride in [`Self::reliefs`], whose tuple only speaks boss/recess. The
+    /// radius is the well radius's parallel curve at the button's inset; the
+    /// depth matches the well's carve, so the seam and the wall read as one
+    /// family.
+    pub fn picker_troughs(&self) -> Vec<(f32, f32, f32, f32, (f32, f32, f32, f32), f32)> {
+        if !self.visible || !crate::layout::control_relief() {
+            return Vec::new();
+        }
+        let mut out = Vec::new();
+        let hidden = self.hidden_rows();
+        for (i, p) in self.display_params.iter().enumerate() {
+            if hidden[i] || !p.2.starts_with("textpick") {
+                continue;
+            }
+            if let (Some(d), Some(tb)) = (&self.choices[i], &self.texts[i]) {
+                let (bx, by, bw, bh) = d.rect();
+                let (_, _, _, th) = tb.rect();
+                let ty = crate::widget::label_offset(tb);
+                if bw > 0.0 && bh > 0.0 {
+                    let depth = crate::layout::bevel_width().min((th - ty) * 0.2);
+                    let r = (crate::layout::textbox_corner_radius() - depth).max(2.0);
+                    out.push((bx, by, bw, bh, (r, r, r, r), depth));
+                }
+            }
+        }
+        out
+    }
+
     /// The sphere companion to [`Self::rounded_quads`]: the slider rows' thumb
     /// knobs, which are `Prim::Sphere` — a prim NO legacy flat view carries, so
     /// a host rendering this panel through the legacy views must read this
@@ -1390,6 +1411,9 @@ impl Paint for ParametersBg {
                 ctx.recess_edges(Rect { x: rx, y: ry, width: rw, height: rh }, radii, rd, edges);
             }
         }
+        for (tx2, ty2, tw2, th2, radii, td) in self.picker_troughs() {
+            ctx.trough(Rect { x: tx2, y: ty2, width: tw2, height: th2 }, radii, td);
+        }
         for (fcx, fcy, fr, fd, fs) in self.section_fillets() {
             ctx.concave_fillet(fcx, fcy, fr, fd, fs, false);
         }