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

commit33d42576def0fbd90a936dbcd7b78e29b413340f
parente203d60124
authorLucas Galante <[email protected]>
date2026-09-06 11:38
feat(relief): flush trough for the slide toggle glider and the params-pane dropdown

The parameters pane drew its dropdown rows as raised bosses while the
Dropdown widget's own raised paint has been a flush inset plate (one
trough on the content band) all along; the pane now mirrors it, carrying
the trigger's seam ring through `troughs()` instead of a boss in
`reliefs()`. The face stays the plate — the pane carries no dropdown fill.

The slide toggle's glider is a trough too: `CarveKind::Trough` joins the
ReliefCarve vocabulary (`PaintCtx::carve` maps it to `trough_edges`),
`Toggle::flat_carves` emits it for the glider, and the pane dispatches the
widget's own carves by kind — rocker steps to `reliefs()`, the glider's
seam to `troughs()` — rather than re-deriving the geometry. The rocker
style is unchanged.

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

 src/layout.rs                         |  5 +++
 src/scene/paint.rs                    |  1 +
 src/widget/container/parameters_bg.rs | 73 ++++++++++++++++++++++++++---------
 src/widget/input/checkbox.rs          | 22 ++++++-----
 4 files changed, 74 insertions(+), 27 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index 6cec05a..9d19c73 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3574,6 +3574,11 @@ pub enum CarveKind {
     Recess { tint: Option<[f32; 3]> },
     /// Interior one step UP ([`crate::scene::paint::PaintCtx::boss_edges`]).
     Boss,
+    /// A FLUSH inset ([`crate::scene::paint::PaintCtx::trough_edges`]): the
+    /// interior stays level with the surface and a valley seam runs the
+    /// boundary — the closed dropdown's chrome, for a control that is part
+    /// of the plate rather than a step up or down from it.
+    Trough,
 }
 
 impl ReliefCarve {
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 87c46bf..290367c 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -1003,6 +1003,7 @@ impl PaintCtx {
             crate::layout::CarveKind::Recess { .. } => {
                 self.recess_edges(rect, c.radii, c.depth, c.edges)
             }
+            crate::layout::CarveKind::Trough => self.trough_edges(rect, c.radii, c.depth, c.edges),
         }
     }
 
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index e4067b9..70a24cf 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1172,32 +1172,34 @@ impl ParametersBg {
                 // 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))
+                // The dropdown trigger is a FLUSH inset control: the widget's
+                // own raised paint is one `inset_plate` (face level with the
+                // plate, a valley seam around it), so its ring travels through
+                // [`Self::troughs`]. A boss here read as a raised island the
+                // widget itself never draws.
+                None
             } else if p.2 == "button" {
                 self.buttons[i].as_ref().map(|w| (w as &dyn WidgetHost, crate::layout::button_corner_radius(), true))
             } else if p.2 == "toggle" || p.2 == "checkbox" {
                 // The rocker's bg pill and its faces' uniform light overlays
                 // (`Toggle::face_light`) arrive through the rounded-quad view;
-                // the entries here are its two flat halves' beveled edges
-                // (`Toggle::rocker_reliefs`, exactly what the widget's own
-                // raised paint emits): the state half a raised plateau, the
-                // other recessed, hinge wall open on both.
+                // the entries here are the widget's own step carves
+                // (`Toggle::flat_carves`, exactly what its paint emits): the
+                // rocker's two flat halves, the state half a raised plateau,
+                // the other recessed, hinge wall open on both. The slide
+                // glider is a trough, not a step, and rides [`Self::troughs`].
                 if let Some(t) = &self.toggles[i] {
                     let (x, y, w, h) = t.rect();
                     if w > 0.0 && h > 0.0 {
                         let ty = crate::widget::label_offset(t);
                         let rect = Rect { x, y: y + ty, width: w, height: h - ty };
-                        let depth = crate::layout::bevel_width().min(rect.height * 0.2);
-                        if let Some(btn) = t.inner().slide_button(rect) {
-                            // Slide style: the gliding half-width button is one
-                            // raised plateau (its fill arrives through the
-                            // rounded-quad view like the rocker's).
-                            let r = crate::layout::toggle_corner_radius();
-                            out.push((btn.x, btn.y, btn.width, btn.height, (r, r, r, r), depth, true, all));
-                        } else {
-                            for (half, radii, walls, raised) in t.inner().rocker_reliefs(rect) {
-                                out.push((half.x, half.y, half.width, half.height, radii, depth, raised, walls));
-                            }
+                        for c in t.inner().flat_carves(rect) {
+                            let raised = match c.kind {
+                                crate::layout::CarveKind::Boss => true,
+                                crate::layout::CarveKind::Recess { .. } => false,
+                                crate::layout::CarveKind::Trough => continue,
+                            };
+                            out.push((c.x, c.y, c.w, c.h, c.radii, c.depth, raised, c.edges));
                         }
                     }
                 }
@@ -1260,8 +1262,10 @@ impl ParametersBg {
     /// The textpick picker buttons' trough rings — `(x, y, w, h, radii, depth)`
     /// for [`crate::scene::paint::PaintCtx::trough_edges`], drawn by the host
     /// AFTER [`Self::reliefs`]. These are the rows' FLUSH inset controls — the
-    /// textpick picker button, and the spinbox's -/+ run: faces level with
-    /// their well floor. On the sides a control adjoins its well, the WELL'S
+    /// textpick picker button, the spinbox's -/+ run, the dropdown trigger
+    /// (the widget's own `inset_plate`) and the slide toggle's glider: faces
+    /// level with the surface they sit in, so the control reads as part of
+    /// the plate, marked off by its seam alone. On the sides a control adjoins its well, the WELL'S
     /// OWN WALL is the seam's far side (the face reaches the wall's base and
     /// that trough edge is suppressed — a lip of its own there doubles the
     /// valley); only edges facing open floor carve their own wall. They
@@ -1304,6 +1308,39 @@ impl ParametersBg {
                         out.push((run.x, run.y, run.width, run.height, radii, rd, edges));
                     }
                 }
+            } else if p.2.starts_with("choice") {
+                // The dropdown trigger: the widget's own raised paint is one
+                // `inset_plate` on its content band — the same ring here, on
+                // the same band (side-label inset, top-label band excluded),
+                // same radius, same depth cap. The face stays the plate: the
+                // pane carries no dropdown fill (a `Border` face never reaches
+                // the rounded-quad view), so the trigger is flush and bare.
+                if let Some(d) = &self.choices[i] {
+                    let (x, y, w, h) = d.rect();
+                    if w > 0.0 && h > 0.0 {
+                        let lx = d.label_x_offset();
+                        let ty = crate::widget::label_offset(d);
+                        let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
+                        let r = crate::layout::dropdown_corner_radius();
+                        out.push((x + lx, y + ty, w - lx, h - ty, (r, r, r, r), depth, (true, true, true, true)));
+                    }
+                }
+            } else if p.2 == "toggle" || p.2 == "checkbox" {
+                // The slide glider's seam ring — the trough entries of the
+                // widget's own carves (`Toggle::flat_carves`); its rocker
+                // steps ride [`Self::reliefs`].
+                if let Some(t) = &self.toggles[i] {
+                    let (x, y, w, h) = t.rect();
+                    if w > 0.0 && h > 0.0 {
+                        let ty = crate::widget::label_offset(t);
+                        let rect = Rect { x, y: y + ty, width: w, height: h - ty };
+                        for c in t.inner().flat_carves(rect) {
+                            if matches!(c.kind, crate::layout::CarveKind::Trough) {
+                                out.push((c.x, c.y, c.w, c.h, c.radii, c.depth, c.edges));
+                            }
+                        }
+                    }
+                }
             }
         }
         out
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 21af135..a8ca78c 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -331,17 +331,20 @@ impl Toggle {
             .collect()
     }
 
-    /// The step carves this Toggle paints — the glider's raised rim in the
-    /// slide style, the rocker's raised/recessed halves otherwise (only under
-    /// `raised` styling; without it the faces' light stands alone). Companion
-    /// to [`Toggle::flat_faces`]; see there for why both exist.
+    /// The carves this Toggle paints — the glider's flush seam ring in the
+    /// slide style (a trough: the pad's face stays level with the plate, the
+    /// closed dropdown's chrome, so only the valley around it and its
+    /// position say where the state is), the rocker's raised/recessed halves
+    /// otherwise (only under `raised` styling; without it the faces' light
+    /// stands alone). Companion to [`Toggle::flat_faces`]; see there for why
+    /// both exist.
     pub fn flat_carves(&self, rect: Rect) -> Vec<crate::layout::ReliefCarve> {
         use crate::layout::{CarveKind, ReliefCarve};
         let radius = crate::layout::toggle_corner_radius();
         let depth = crate::layout::bevel_width().min(rect.height * 0.2);
         if let Some(btn) = self.slide_button(rect) {
             return vec![ReliefCarve {
-                kind: CarveKind::Boss,
+                kind: CarveKind::Trough,
                 x: btn.x,
                 y: btn.y,
                 w: btn.width,
@@ -473,10 +476,11 @@ impl Paint for Toggle {
         if slide {
             // The slide style: the widget is the track; a half-width button
             // glides between its ends with the state (animated in `tick`).
-            // With no fill the button is a bare raised pad of the plate — its
-            // beveled rim and its position ARE the read (left off, right on).
-            // The rim also reaches legacy-view hosts through `slide_button`
-            // (see `ParametersBg::reliefs`).
+            // With no fill the button is a flush inset pad of the plate — the
+            // valley seam around it (a trough, the closed dropdown's chrome)
+            // and its position ARE the read (left off, right on). The seam
+            // also reaches legacy-view hosts through `flat_carves` (see
+            // `ParametersBg::troughs`).
             for carve in self.flat_carves(rect) {
                 ctx.carve(&carve);
             }