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

commit26deadb2887b4cd0349186df31ff8ede6ab1376a
parentedd2936875
authorLucas Galante <[email protected]>
date2026-07-30 10:15
feat: slide toggle button is glass — translucent fill, unconditional bevel

The gliding button's crossfaded state fill is knocked down to a
mostly-transparent tint (style.control.toggle.button_opacity, default
0.25) so the track and pane show through, and its beveled rim now draws in
both DE styles rather than only under control_relief — the rim is the
style's defining edge; the glass fill alone wouldn't read.

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

 src/layout.rs                |  9 +++++++++
 src/widget/input/checkbox.rs | 25 +++++++++++++++----------
 2 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index 28d4371..d34e190 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -106,6 +106,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.control.textbox.multiline.line_wrap" | "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
                 "style.control.textbox.multiline.border_width" | "style.textbox.multiline.border_width" | "style.data.textbox.multiline.border_width" => "textbox_multiline_border_width",
                 "style.control.toggle.style" => "toggle_style",
+                "style.control.toggle.button_opacity" => "toggle_button_opacity",
                 "style.control.toggle.height" => "toggle_height",
                 "style.control.toggle.border_width" => "toggle_border_width",
                 "style.control.toggle.disabled_color" => "toggle_disabled_color",
@@ -1771,6 +1772,14 @@ pub fn toggle_slide() -> bool {
         .is_some_and(|s| s == "slide")
 }
 
+/// The slide toggle button's fill opacity (`style.control.toggle.button_opacity`):
+/// a mostly-transparent glass tint over the track — the beveled rim carries the
+/// button's read.
+pub fn toggle_button_opacity() -> f32 {
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("toggle_button_opacity").unwrap_or(0.25)
+}
+
 /// The slider's render style: `style.control.slider.style = "band"` swaps the
 /// track/fill/thumb for a thin full-range band that inflates smoothly at the
 /// value (see `Slider::paint`). Anything else — or unset — keeps the default
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 279e04f..1a4fcc7 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -291,7 +291,10 @@ impl Toggle {
     }
 
     /// The slide button's face color: the off/on state colors crossfaded by
-    /// the animated position, so the fill morphs while the button glides.
+    /// the animated position, so the fill morphs while the button glides —
+    /// then knocked down to a mostly-transparent glass tint
+    /// (`style.control.toggle.button_opacity`): the beveled edges carry the
+    /// button's read, the fill is a whisper over the track.
     pub fn slide_button_color(&self) -> [f32; 4] {
         let off = colors::toggle_off_color();
         let on = colors::toggle_on_color();
@@ -300,7 +303,7 @@ impl Toggle {
             off[0] + (on[0] - off[0]) * t,
             off[1] + (on[1] - off[1]) * t,
             off[2] + (on[2] - off[2]) * t,
-            off[3] + (on[3] - off[3]) * t,
+            (off[3] + (on[3] - off[3]) * t) * crate::layout::toggle_button_opacity(),
         ]
     }
 
@@ -435,15 +438,17 @@ impl Paint for Toggle {
             }
             if let Some(btn) = self.slide_button(rect) {
                 let fill = self.slide_button_color();
-                if radius > 0.0 {
-                    ctx.rounded_rect(btn, radius, (true, true, true, true), fill);
-                } else {
-                    ctx.quad(btn, fill);
-                }
-                if self.raised {
-                    let depth = crate::layout::bevel_width().min(h * 0.2);
-                    ctx.boss_edges(btn, (radius, radius, radius, radius), depth, (true, true, true, true));
+                if fill[3] > 0.003 {
+                    if radius > 0.0 {
+                        ctx.rounded_rect(btn, radius, (true, true, true, true), fill);
+                    } else {
+                        ctx.quad(btn, fill);
+                    }
                 }
+                // The beveled rim is the style's defining edge — the glass
+                // fill alone wouldn't read — so it draws in both DE styles.
+                let depth = crate::layout::bevel_width().min(h * 0.2);
+                ctx.boss_edges(btn, (radius, radius, radius, radius), depth, (true, true, true, true));
             }
         } else if self.raised {
             // The rocker: two FLAT half faces (see `rocker_reliefs`) — the