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

commit9510eb26e84cebc578e3672c77a72f833527cf5a
parent5b5e704063
authorLucas Galante <[email protected]>
date2026-07-30 22:20
fix: standalone build + green tests — complete ArcShaded, fence app_chord doc

75f7132 committed model.rs's Prim::ArcShaded pass-through while the
primitive itself was still uncommitted work-in-progress, so a standalone
clone stopped compiling. Land the primitive's substrate: the Prim variant
+ PaintCtx::arc_shaded (paint.rs) and the tessellate arm +
push_arc_shaded_vertices (window_runner.rs) — the ramp widget work that
CONSUMES it stays in progress. Also fence the app_chord doc example as
text: rustdoc compiled the indented block as a doctest and it isn't Rust.

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

 src/backend/window_runner.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++
 src/input.rs                 |  6 +++--
 src/scene/paint.rs           | 32 ++++++++++++++++++++++++++
 3 files changed, 91 insertions(+), 2 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 6b268fa..029e766 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1747,6 +1747,9 @@ pub fn tessellate_display_list(
             Prim::Arc { cx, cy, radius, thickness, start: sa, end: ea, color } => {
                 push_arc_background_vertices(*cx, *cy, *radius, *thickness, *sa, *ea, sw, sh, *color, segs(*radius), no, &mut verts);
             }
+            Prim::ArcShaded { cx, cy, radius, thickness, start: sa, end: ea, inner, crest, outer } => {
+                push_arc_shaded_vertices(*cx, *cy, *radius, *thickness, *sa, *ea, sw, sh, *inner, *crest, *outer, segs(*radius), no, &mut verts);
+            }
             Prim::Vector { x1, y1, x2, y2, thickness, color, cap } => {
                 let lc = match cap {
                     Cap::Flat => LineCap::Flat,
@@ -2234,6 +2237,58 @@ pub fn push_arc_background_vertices(
     }
 }
 
+/// A ring band with radial Gouraud shading: two sub-bands (inner rim → crest
+/// centerline, crest → outer rim) whose vertex colors interpolate across the
+/// stroke — the rounded-bevel profile — plus the half-px alpha feathers at
+/// both true rims (colors matched to the adjacent band, so no seams).
+#[allow(clippy::too_many_arguments)]
+pub fn push_arc_shaded_vertices(
+    cx: f32, cy: f32, r: f32,
+    thickness: f32,
+    start_angle: f32, end_angle: f32,
+    sw: f32, sh: f32,
+    inner: [f32; 4], crest: [f32; 4], outer: [f32; 4],
+    segments: usize,
+    clip_circle: [f32; 3],
+    out: &mut Vec<Vertex>,
+) {
+    let f = 0.5f32.min(thickness * 0.25);
+    let r_out = r;
+    let r_in = (r - thickness).max(0.0);
+    let r_mid = (r_in + r_out) / 2.0;
+    let fade_in = [inner[0], inner[1], inner[2], 0.0];
+    let fade_out = [outer[0], outer[1], outer[2], 0.0];
+    // (inner radius, outer radius, color at inner edge, color at outer edge)
+    let bands = [
+        ((r_in - f).max(0.0), r_in + f, fade_in, inner),
+        (r_in + f, r_mid, inner, crest),
+        (r_mid, r_out - f, crest, outer),
+        (r_out - f, r_out + f, outer, fade_out),
+    ];
+    for i in 0..segments {
+        let theta1 = start_angle + (i as f32) * (end_angle - start_angle) / (segments as f32);
+        let theta2 = start_angle + ((i + 1) as f32) * (end_angle - start_angle) / (segments as f32);
+        let (c1, s1) = (theta1.cos(), theta1.sin());
+        let (c2, s2) = (theta2.cos(), theta2.sin());
+        for &(ra, rb, ca, cb) in &bands {
+            if rb <= ra {
+                continue;
+            }
+            let p = |rad: f32, c: f32, s: f32| -> [f32; 2] {
+                [((cx + rad * c) / sw) * 2.0 - 1.0, 1.0 - ((cy + rad * s) / sh) * 2.0]
+            };
+            let (i1, o1) = (p(ra, c1, s1), p(rb, c1, s1));
+            let (i2, o2) = (p(ra, c2, s2), p(rb, c2, s2));
+            out.push(Vertex { position: i1, color: ca, clip_circle });
+            out.push(Vertex { position: o1, color: cb, clip_circle });
+            out.push(Vertex { position: o2, color: cb, clip_circle });
+            out.push(Vertex { position: i1, color: ca, clip_circle });
+            out.push(Vertex { position: o2, color: cb, clip_circle });
+            out.push(Vertex { position: i2, color: ca, clip_circle });
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct WindowSettings {
     pub title: String,
diff --git a/src/input.rs b/src/input.rs
index 5b6e6cf..7b70c76 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -353,8 +353,10 @@ pub fn cached() -> &'static InputConfig {
 /// the compiled-in default. The app domain is the binary name. This is the
 /// standard way for a client to resolve its shortcuts at startup:
 ///
-///     let open = cce_ui::input::app_chord("open_file", "enter");
-///     ... cce_ui::widget::match_key_shortcut(event, &open) ...
+/// ```text
+/// let open = cce_ui::input::app_chord("open_file", "enter");
+/// ... cce_ui::widget::match_key_shortcut(event, &open) ...
+/// ```
 pub fn app_chord(name: &str, default: &str) -> String {
     let app = crate::config::get_app_name().unwrap_or_default();
     cached().resolve_chord(&app, name, default)
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 097fb7f..6b9253f 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -91,6 +91,10 @@ pub enum Prim {
     /// offset (the shading amplitude is the DE-wide `bevel_depth`).
     Plate { rect: Rect, radii: Radii, color: [f32; 4], depth: f32 },
     Arc { cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4] },
+    /// A ring band with radial color interpolation — inner rim → crest
+    /// (centerline) → outer rim — for rounded rim bevels (the Ramp's key
+    /// rings). `radius` is the stroke's outer edge, like `Arc`.
+    ArcShaded { cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, inner: [f32; 4], crest: [f32; 4], outer: [f32; 4] },
     Vector { x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: [f32; 4], cap: Cap },
     Circle { cx: f32, cy: f32, radius: f32, color: [f32; 4] },
     /// A `Circle` lit as a ball: the disc is shaded per pixel as a hemisphere
@@ -521,6 +525,34 @@ impl PaintCtx {
         self.push(Prim::Arc { cx: cx + ox, cy: cy + oy, radius, thickness, start, end, color });
     }
 
+    /// A radially-shaded ring band — see [`Prim::ArcShaded`].
+    #[allow(clippy::too_many_arguments)]
+    pub fn arc_shaded(
+        &mut self,
+        cx: f32,
+        cy: f32,
+        radius: f32,
+        thickness: f32,
+        start: f32,
+        end: f32,
+        inner: [f32; 4],
+        crest: [f32; 4],
+        outer: [f32; 4],
+    ) {
+        let (ox, oy) = self.offset;
+        self.push(Prim::ArcShaded {
+            cx: cx + ox,
+            cy: cy + oy,
+            radius,
+            thickness,
+            start,
+            end,
+            inner,
+            crest,
+            outer,
+        });
+    }
+
     pub fn text(&mut self, text: impl Into<String>, x: f32, y: f32, font_size: f32, color: [u8; 3]) {
         self.text_with(text, x, y, font_size, color, None, None);
     }