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

commit46884b3f2b5c4c93baa4fff36d03856594287ac9
parentc4802d12ab
authorLucas Galante <[email protected]>
date2026-08-03 12:33
feat: band-style slider gets capsule ends

The band's height profile shrinks over a circular cap inside each track
end (single source: the fill, wheel halo, and well contour all round
together), the flat runs sample per-column through the tip regions, and
the well's square end walls are replaced by the contour wrapping the
tips on a WELL_GAP circle — the carve closes round, like the rest of
the snake.

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

 src/widget/input/slider.rs | 69 +++++++++++++++++++++++++++++++---------------
 1 file changed, 47 insertions(+), 22 deletions(-)

diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index a7b8319..08f879d 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -212,7 +212,17 @@ impl Slider {
         let vx = g.track_x + self.value * g.track_w;
         let t = ((x - vx) / bulge_w).clamp(-1.0, 1.0);
         let bell = 0.5 * (1.0 + (std::f32::consts::PI * t).cos());
-        band_t + (bulge_h - band_t) * bell.powf(1.35)
+        let h = band_t + (bulge_h - band_t) * bell.powf(1.35);
+        // Capsule tips: the profile shrinks over a circular cap inside each
+        // track end — the band ends round, not square-cut, and the well
+        // contour and wheel halo (both measured from here) round with it.
+        let d = (x - g.track_x).min(g.track_x + g.track_w - x);
+        let r = (h * 0.5).max(0.5);
+        if d < r {
+            let t = ((r - d.max(0.0)) / r).min(1.0);
+            return h * (1.0 - t * t).max(0.0).sqrt();
+        }
+        h
     }
 
     /// The wheel-capture zone. Band style: an inset halo around the DRAWN
@@ -272,8 +282,19 @@ impl Slider {
         let sub = WELL_WALL / WALL_STEPS as f32;
         for i in 0..cols {
             let x = wx0 + i as f32 * colw;
-            let xm = (x + colw * 0.5).clamp(g.track_x, g.track_x + g.track_w);
-            let c = self.band_height_at(g, xm) * 0.5 + WELL_GAP;
+            let xm = x + colw * 0.5;
+            // Inside the track the contour rides the band profile; past the
+            // tips it wraps around them on a WELL_GAP circle — rounded well
+            // ends, not square-cut walls.
+            let c = if xm < g.track_x {
+                let e = g.track_x - xm;
+                (WELL_GAP * WELL_GAP - e * e).max(0.0).sqrt()
+            } else if xm > g.track_x + g.track_w {
+                let e = xm - (g.track_x + g.track_w);
+                (WELL_GAP * WELL_GAP - e * e).max(0.0).sqrt()
+            } else {
+                self.band_height_at(g, xm) * 0.5 + WELL_GAP
+            };
             for k in 0..WALL_STEPS {
                 let fade = 1.0 - k as f32 / WALL_STEPS as f32;
                 // Shadow INSIDE the well below the top contour; the lit lip
@@ -288,32 +309,36 @@ impl Slider {
                 );
             }
         }
-        // End walls close the well: shadow inside the left end, the lit lip
-        // outside the right, same light.
-        let c0 = self.band_height_at(g, g.track_x) * 0.5 + WELL_GAP;
-        let c1 = self.band_height_at(g, g.track_x + g.track_w) * 0.5 + WELL_GAP;
-        for k in 0..WALL_STEPS {
-            let fade = 1.0 - k as f32 / WALL_STEPS as f32;
-            ctx.quad(
-                Rect { x: wx0 + k as f32 * sub, y: cy - c0, width: sub, height: 2.0 * c0 },
-                [0.0, 0.0, 0.0, a_dark * fade],
-            );
-            ctx.quad(
-                Rect { x: wx1 + k as f32 * sub, y: cy - c1, width: sub, height: 2.0 * c1 },
-                [1.0, 1.0, 1.0, a_light * fade],
-            );
-        }
-
-        // Flat runs outside the bulge span.
+        // Flat runs outside the bulge span: one long quad each, except the
+        // capsule-tip regions, which sample the rounded profile per column.
         let l0 = g.track_x;
         let r1 = g.track_x + g.track_w;
         let b0 = (vx - bulge_w).max(l0);
         let b1 = (vx + bulge_w).min(r1);
+        let tip_columns = |ctx: &mut PaintCtx, from: f32, to: f32| {
+            let steps = ((to - from).ceil() as i32).max(1);
+            let sw = (to - from) / steps as f32;
+            for i in 0..steps {
+                let x = from + i as f32 * sw;
+                let h = self.band_height_at(g, x + sw * 0.5);
+                ctx.quad(Rect { x, y: cy - h * 0.5, width: sw + 0.3, height: h }, color);
+            }
+        };
         if b0 > l0 {
-            ctx.quad(Rect { x: l0, y: cy - band_t * 0.5, width: b0 - l0, height: band_t }, color);
+            let flat_start = (l0 + band_t).min(b0);
+            tip_columns(ctx, l0, flat_start);
+            ctx.quad(
+                Rect { x: flat_start, y: cy - band_t * 0.5, width: b0 - flat_start, height: band_t },
+                color,
+            );
         }
         if r1 > b1 {
-            ctx.quad(Rect { x: b1, y: cy - band_t * 0.5, width: r1 - b1, height: band_t }, color);
+            let flat_end = (r1 - band_t).max(b1);
+            ctx.quad(
+                Rect { x: b1, y: cy - band_t * 0.5, width: flat_end - b1, height: band_t },
+                color,
+            );
+            tip_columns(ctx, flat_end, r1);
         }
 
         // The swell: symmetric about the band's centerline. The bell is raised