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

commite6de8d66d1b58215bf9cd7467cc4f65a21830a5c
parent21037f0fda
authorLucas Galante <[email protected]>
date2026-07-22 11:50
style: rocker toggle faces carry beveled edges

The raised Toggle's two angled faces now read as true surfaces: the
state half's lip rises out of the plate (boss_edges), the other falls
away (recess_edges), hinge wall open on both so the faces meet in a
crease. Geometry lives in Toggle::rocker_reliefs — consumed by the
widget's own paint and by ParametersBg::reliefs, whose toggle branch
previously emitted nothing, so legacy-view hosts (cce-designer) lost
the relief styling entirely.

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

 src/widget/container/parameters_bg.rs | 16 ++++++++++++-
 src/widget/input/checkbox.rs          | 44 +++++++++++++++++++++++++++++++----
 2 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 11b250e..70afda9 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1013,7 +1013,21 @@ impl ParametersBg {
                 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 flat faces are RoundedRect prims — they arrive
-                // through the rounded-quad view; no relief entries.
+                // through the rounded-quad view. The entries here are the
+                // faces' beveled edges (`Toggle::rocker_reliefs`, exactly what
+                // the widget's own raised paint emits): the state half's lip
+                // rises, the other falls away, hinge wall open on both.
+                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);
+                        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));
+                        }
+                    }
+                }
                 None
             } else {
                 if let Some(s) = &self.sliders[i] {
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index e0ab62c..4995e4b 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -226,9 +226,10 @@ pub struct Toggle {
     /// Where the label sits across the pill. Mirrors `Button::justify` — same enum, same
     /// 8px edge inset — so the two read as one control set wherever they share a column.
     justify: Justification,
-    /// Raised style: the pill renders as a rocker — the state half raised, the
-    /// other recessed (`rocker_reliefs`) — and the flat style's state gradient
-    /// is dropped.
+    /// Raised style: the pill renders as a rocker — two angled faces with
+    /// beveled edges, the state half raised, the other recessed (see
+    /// `rocker_faces` and the paint impl) — and the flat style's state
+    /// gradient is dropped.
     raised: bool,
 }
 
@@ -290,6 +291,28 @@ impl Toggle {
             [(bottom, bottom_corners, LIT), (top, top_corners, SHADED)]
         }
     }
+
+    /// The rocker faces' beveled edges over `rect`: (half rect, per-corner
+    /// radii, (top, right, bottom, left) walls, raised). Companion to
+    /// `rocker_faces`, in the same order — the state half's lip rises out of
+    /// the surface (raised → `boss_edges`), the other falls away
+    /// (`recess_edges`). The hinge wall is open on both so the faces meet in
+    /// a crease, the recessed side reading as a step down from the raised
+    /// one rather than a double-shaded trough.
+    pub fn rocker_reliefs(
+        &self,
+        rect: Rect,
+    ) -> [(Rect, (f32, f32, f32, f32), (bool, bool, bool, bool), bool); 2] {
+        let r = crate::layout::toggle_corner_radius();
+        let mut state_half = true;
+        self.rocker_faces(rect).map(|(half, corners, _)| {
+            let is_top = corners.0;
+            let radii = if is_top { (r, r, 0.0, 0.0) } else { (0.0, 0.0, r, r) };
+            let walls = if is_top { (true, true, false, true) } else { (false, true, true, true) };
+            let raised = std::mem::take(&mut state_half);
+            (half, radii, walls, raised)
+        })
+    }
 }
 
 impl Adapted<Toggle> {
@@ -351,7 +374,12 @@ impl Paint for Toggle {
         if self.raised {
             // The rocker: two flat angled faces pivoting about the midline
             // (see `rocker_faces`) — the physical read of the flat style's
-            // state gradient, which this style drops entirely.
+            // state gradient, which this style drops entirely. Each face
+            // carries a beveled edge along its outer perimeter: the state
+            // face's lip rises (boss), the other's falls away (recess). The
+            // hinge wall is omitted on both so the faces still meet in a
+            // crease, the recessed side reading as a step down from the
+            // raised one.
             if bg[3] > 0.001 {
                 if radius > 0.0 {
                     ctx.rounded_rect(rect, radius, (true, true, true, true), bg);
@@ -362,6 +390,14 @@ impl Paint for Toggle {
             for (half, corners, tint) in self.rocker_faces(rect) {
                 ctx.rounded_rect(half, radius, corners, tint);
             }
+            let depth = crate::layout::bevel_width().min(h * 0.2);
+            for (half, radii, walls, raised) in self.rocker_reliefs(rect) {
+                if raised {
+                    ctx.boss_edges(half, radii, depth, walls);
+                } else {
+                    ctx.recess_edges(half, radii, depth, walls);
+                }
+            }
         } else if radius > 0.0 {
             ctx.rounded_rect(rect, radius, (true, true, true, true), bg);
         } else {