git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit4e816c46c4f4a731fff87b16678589bdb3e14b12
parent418f1fbf79
authorLucas Galante <[email protected]>
date2026-08-06 10:07
feat: light module circle wears the module-box fill (sphere-lit when raised)

The circle takes box_bg_color — same fill, opacity and per-pixel
compositor backdrop blur as every module box — instead of the bare
ring. With box_bevel=raised the disc draws through the Sphere prim,
the circular sibling of the lit-plate treatment; its highlight azimuth
IS light_source_position, so the module now visually indicates the
very direction it stands for. Flat Circle prim otherwise; the optional
ring stroke stays available (thickness > 0) but is off.

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

 src/main.rs    | 24 +++++++++++++++++++-----
 src/modules.rs | 21 ++++++++-------------
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 5061102..59745ca 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1453,14 +1453,28 @@ impl cce_ui::engine::Application for StatusApp {
                 if rb.corners.2 { rb.radius } else { 0.0 },
                 if rb.corners.3 { rb.radius } else { 0.0 },
             );
-            // Outlined shapes: no bevel treatment. A full circle draws as an
-            // Arc ring — the rounded-rect corner family is the squircle
-            // (corner_shape), which reads as a rounded SQUARE at half-extent
-            // radius — anything else as a Border prim (fill + stroke).
+            // True-shape boxes (the light module's circle): no bevel
+            // treatment. A full circle draws through the Circle/Arc prims —
+            // the rounded-rect corner family is the squircle (corner_shape),
+            // which reads as a rounded SQUARE at half-extent radius — with
+            // the fill and stroke each optional. Anything else outlined goes
+            // through the Border prim (fill + stroke).
             if let Some((border_color, thickness)) = rb.border {
                 if (rb.w - rb.h).abs() < 0.5 && (rb.radius - rb.w / 2.0).abs() < 0.5 {
                     let r = rb.w / 2.0;
-                    pc.arc(rb.x + r, rb.y + r, r, thickness, 0.0, std::f32::consts::TAU, border_color);
+                    if rb.color[3] > 0.001 {
+                        // With raised module boxes (lit plates), the circle
+                        // takes the sphere-lit disc — the circular sibling of
+                        // the plate treatment, same light and material.
+                        if matches!(self.box_bevel, Some(StatusBoxBevel::Raised)) {
+                            pc.sphere(rb.x + r, rb.y + r, r, rb.color);
+                        } else {
+                            pc.circle(rb.x + r, rb.y + r, r, rb.color);
+                        }
+                    }
+                    if thickness > 0.05 {
+                        pc.arc(rb.x + r, rb.y + r, r, thickness, 0.0, std::f32::consts::TAU, border_color);
+                    }
                 } else {
                     pc.border(rect, radii, rb.color, border_color, thickness);
                 }
diff --git a/src/modules.rs b/src/modules.rs
index 4f548d2..f4c38b4 100644
--- a/src/modules.rs
+++ b/src/modules.rs
@@ -878,7 +878,7 @@ impl StatusModule for LightSourceModule {
         _font_system: &mut FontSystem,
         _font_family: &str,
         _font_size: f32,
-        normal_color: [f32; 4],
+        _normal_color: [f32; 4],
         bar_h: f32,
         _scale_factor: f64,
         _text_prims: &mut Vec<crate::TextPrim>,
@@ -888,30 +888,25 @@ impl StatusModule for LightSourceModule {
         _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
-        _box_bg_color: Option<[f32; 4]>,
+        box_bg_color: Option<[f32; 4]>,
         _status_box_radius: f32,
         rounded_boxes: &mut Vec<RoundedBox>,
         _padding: f32,
     ) {
         let d = w.min(bar_h);
-        // normal_color is raw sRGB (a text color); the ring draws through the
-        // quad pipeline, which expects linear — convert so the stroke reads
-        // as the same shade as module text.
-        let ring = [
-            normal_color[0].powf(2.2),
-            normal_color[1].powf(2.2),
-            normal_color[2].powf(2.2),
-            1.0,
-        ];
+        // The circle wears the module-box fill: same color, opacity and
+        // (per-pixel, compositor-side) backdrop blur as every other module's
+        // box — just circle-shaped and empty of content.
         rounded_boxes.push(RoundedBox {
             x: x + (w - d) / 2.0,
             y: (bar_h - d) / 2.0,
             w: d,
             h: d,
             radius: d / 2.0,
-            color: [0.0, 0.0, 0.0, 0.0],
+            color: box_bg_color.unwrap_or([0.0, 0.0, 0.0, 0.0]),
             corners: (true, true, true, true),
-            border: Some((ring, 1.5)),
+            // Circle marker; zero thickness = fill only, no stroke.
+            border: Some(([0.0; 4], 0.0)),
         });
     }
 }