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

commita86920b922525acfaa3b02bbc2ccece4720c6d1c
parent111dd552d8
authorLucas Galante <[email protected]>
date2026-08-04 12:18
feat: configurable bevel effect on the module boxes

New style keys, canonical at style { status ... } in config.kdl:
- box_bevel="raised"|"inset" — raised draws each module box as a lit
  plate (fill + rolled lip); inset keeps the flat fill and carves a
  recess rim over it. Absent/other values keep the flat boxes.
- box_bevel_depth=(f64)N — roll width of the lip in logical px
  (default 3.0; the DE-wide bevel_width is window-scale and far too
  fat for a bar box).

Applied in the display_list replay, so every RoundedBox gets the
treatment uniformly — the per-module boxes and the window module's
viewport tabs alike, with the same positional corner→radius mapping
the RoundedRect prim uses. Config reloads live via the existing
mtime poll in tick().

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

 src/config.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++
 src/main.rs   | 38 ++++++++++++++++++++++++++++++++++----
 2 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index 6c492b4..3f319c4 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -225,6 +225,32 @@ pub(crate) fn read_status_box_corner_radius_from_config() -> f32 {
     cfg_f32("/style/status/box_corner_radius", "status_box_corner_radius").unwrap_or(4.0)
 }
 
+/// Bevel treatment for the module boxes.
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub(crate) enum StatusBoxBevel {
+    /// A lit plate lip — the box rises out of the bar.
+    Raised,
+    /// A carved recess rim — the box sinks into the bar.
+    Inset,
+}
+
+/// `style { status box_bevel="raised"|"inset" }`; absent, `"none"`, or any
+/// other value keeps the flat boxes.
+pub(crate) fn read_status_box_bevel_from_config() -> Option<StatusBoxBevel> {
+    match cfg_string("/style/status/box_bevel", "status_box_bevel")?.to_ascii_lowercase().as_str() {
+        "raised" => Some(StatusBoxBevel::Raised),
+        "inset" => Some(StatusBoxBevel::Inset),
+        _ => None,
+    }
+}
+
+/// `style { status box_bevel_depth=(f64)N }` — the roll width of the bevel lip
+/// in logical px. The DE-wide `bevel_width` (~9px) is window-scale; module
+/// boxes in a ~24px bar want a much tighter lip.
+pub(crate) fn read_status_box_bevel_depth_from_config() -> f32 {
+    cfg_f32("/style/status/box_bevel_depth", "status_box_bevel_depth").unwrap_or(3.0)
+}
+
 pub(crate) fn get_ccectl_cmd() -> String {
     if let Ok(home) = std::env::var("HOME") {
         let path = format!("{}/.local/bin/ccectl", home);
@@ -279,6 +305,25 @@ style {
         );
     }
 
+    #[test]
+    fn test_box_bevel_config() {
+        let val = parse_kdl(
+            r##"
+style {
+    status box_bevel="raised" box_bevel_depth=(f64)2.5
+}
+"##,
+        );
+        assert_eq!(
+            val.pointer("/style/status/box_bevel").and_then(|v| v.as_str()),
+            Some("raised")
+        );
+        assert_eq!(
+            val.pointer("/style/status/box_bevel_depth").and_then(|v| v.as_f64()),
+            Some(2.5)
+        );
+    }
+
     // --- json_find_key (legacy fuzzy fallback) ---
 
     #[test]
diff --git a/src/main.rs b/src/main.rs
index 22fffaf..490e896 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -244,6 +244,8 @@ struct StatusApp {
     height: u32,
     needs_rebuild: bool,
     current_bg_color: [f32; 4],
+    box_bevel: Option<StatusBoxBevel>,
+    box_bevel_depth: f32,
     input_regions: Vec<(i32, i32, i32, i32)>,
     module_bounds: Vec<ModuleBounds>,
     left_modules: Vec<Box<dyn StatusModule>>,
@@ -328,6 +330,8 @@ impl StatusApp {
 
         let box_bg_color = read_status_box_background_color_from_config();
         let status_box_radius = read_status_box_corner_radius_from_config();
+        self.box_bevel = read_status_box_bevel_from_config();
+        self.box_bevel_depth = read_status_box_bevel_depth_from_config();
 
         self.status_bar.set_rect(0.0, 0.0, self.width as f32, self.height as f32);
         if self.selected_module_name.is_some() {
@@ -993,6 +997,8 @@ impl cce_ui::engine::Application for StatusApp {
             height: read_status_height_from_config() as u32,
             needs_rebuild: true,
             current_bg_color: color::STATUS_BG,
+            box_bevel: None,
+            box_bevel_depth: 3.0,
             input_regions: Vec::new(),
             module_bounds: Vec::new(),
             left_modules,
@@ -1157,10 +1163,34 @@ impl cce_ui::engine::Application for StatusApp {
 
         for rb in &self.rounded_boxes {
             let rect = Rect { x: rb.x, y: rb.y, width: rb.w, height: rb.h };
-            if rb.radius > 0.1 {
-                pc.rounded_rect(rect, rb.radius, rb.corners, rb.color);
-            } else {
-                pc.quad(rect, rb.color);
+            // Same positional corner→radius mapping the RoundedRect prim uses.
+            let radii = (
+                if rb.corners.0 { rb.radius } else { 0.0 },
+                if rb.corners.1 { rb.radius } else { 0.0 },
+                if rb.corners.2 { rb.radius } else { 0.0 },
+                if rb.corners.3 { rb.radius } else { 0.0 },
+            );
+            match self.box_bevel {
+                Some(StatusBoxBevel::Raised) => {
+                    // A lit plate: fill + rolled lip in one prim.
+                    pc.bevel(rect, radii, rb.color, self.box_bevel_depth);
+                }
+                Some(StatusBoxBevel::Inset) => {
+                    // Recess shades only the rim, so keep the flat fill under it.
+                    if rb.radius > 0.1 {
+                        pc.rounded_rect(rect, rb.radius, rb.corners, rb.color);
+                    } else {
+                        pc.quad(rect, rb.color);
+                    }
+                    pc.recess(rect, radii, self.box_bevel_depth);
+                }
+                None => {
+                    if rb.radius > 0.1 {
+                        pc.rounded_rect(rect, rb.radius, rb.corners, rb.color);
+                    } else {
+                        pc.quad(rect, rb.color);
+                    }
+                }
             }
         }