status bar
git clone https://git.lucas.co/cce-status-interface.git
feat: light module is an empty circle; radians value moves to its menu
The strip presence is now just an unfilled ring, bar-thickness
diameter, no module box and no text — the radians value shows as an
inert first row of the module's context menu instead. RoundedBox
grows an optional border (color, thickness): display_list draws a
marked full circle through the Arc prim (a true ring — the
rounded-rect corner family follows corner_shape, which reads as a
rounded square at half-extent radius) and any other outlined box
through the Border prim, both skipping the bevel treatment.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/main.rs | 32 ++++++++++++++++++++++++++++++-
src/modules.rs | 59 ++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 70 insertions(+), 21 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 7b92402..5061102 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -275,6 +275,10 @@ pub struct RoundedBox {
pub radius: f32,
pub color: [f32; 4],
pub corners: (bool, bool, bool, bool),
+ /// Some((color, thickness)): draw as an outlined shape — `color` fills
+ /// (pass transparent for an empty ring) and the stroke uses this color
+ /// and thickness. Skips the box bevel treatment.
+ pub border: Option<([f32; 4], f32)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -469,6 +473,7 @@ impl StatusApp {
radius: status_box_radius,
color,
corners: if self.selected_module_name.is_some() { (true, true, true, true) } else { (false, false, true, true) },
+ border: None,
});
}
}
@@ -552,6 +557,7 @@ impl StatusApp {
radius: status_box_radius,
color,
corners: if self.selected_module_name.is_some() { (true, true, true, true) } else { (false, false, true, true) },
+ border: None,
});
}
}
@@ -752,6 +758,7 @@ impl StatusApp {
radius: status_box_radius.max(4.0),
color: box_bg_color.unwrap_or([0.055, 0.055, 0.075, 0.97]),
corners: (true, true, true, true),
+ border: None,
});
let text_u8 = [
@@ -1446,6 +1453,19 @@ 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).
+ 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);
+ } else {
+ pc.border(rect, radii, rb.color, border_color, thickness);
+ }
+ continue;
+ }
match self.box_bevel {
Some(StatusBoxBevel::Raised) => {
// A lit plate: fill + rolled lip in one prim.
@@ -1745,7 +1765,7 @@ impl cce_ui::engine::Application for StatusApp {
separator: false,
action: MenuRowAction::Dispatch(ev),
};
- let rows = if self.adjust_position_mode {
+ let mut rows = if self.adjust_position_mode {
vec![dispatch_row("Done", CustomEvent::ToggleAdjustPositionMode)]
} else {
vec![
@@ -1756,6 +1776,16 @@ impl cce_ui::engine::Application for StatusApp {
dispatch_row("Adjust Positions", CustomEvent::ToggleAdjustPositionMode),
]
};
+ // The light module's strip presence is just the empty
+ // circle; its value lives here in the menu.
+ if mb.name == "light_source" {
+ rows.insert(0, MenuRow {
+ label: format!("{:.2} rad", modules::get_light_source_pos_from_config()),
+ enabled: true,
+ separator: false,
+ action: MenuRowAction::Inert,
+ });
+ }
self.context_menu = Some(ModuleContextMenu {
pages: vec![MenuPage { title: mb.name.clone(), rows }],
page: 0,
diff --git a/src/modules.rs b/src/modules.rs
index e36d10a..4f548d2 100644
--- a/src/modules.rs
+++ b/src/modules.rs
@@ -225,6 +225,7 @@ impl StatusModule for WindowModule {
radius: status_box_radius,
color,
corners: (false, false, true, true),
+ border: None,
});
}
crate::draw_label(text_prims, label, cur_x + padding, centered_text_y(bar_h, font_size));
@@ -828,7 +829,7 @@ impl StatusModule for TrayModule {
pub struct LightSourceModule;
-fn get_light_source_pos_from_config() -> f32 {
+pub(crate) fn get_light_source_pos_from_config() -> f32 {
let val = crate::get_cached_config();
if let Some(pos_val) = crate::json_find_key(&val, "light_source_position") {
if let Some(f) = pos_val.as_f64() {
@@ -846,39 +847,41 @@ fn get_light_source_pos_from_config() -> f32 {
impl StatusModule for LightSourceModule {
fn name(&self) -> &'static str { "light_source" }
+ // The module is just the empty circle — no module box behind it.
+ fn has_custom_background(&self, _title: &str) -> bool { true }
+
fn width(
&self,
_stats: &Option<SystemStats>,
_viewport: &str,
_layout: &str,
_title: &str,
- font_system: &mut FontSystem,
- font_family: &str,
- font_size: f32,
+ _font_system: &mut FontSystem,
+ _font_family: &str,
+ _font_size: f32,
_tray_items: &HashMap<String, TrayItem>,
- padding: f32,
+ _padding: f32,
) -> f32 {
- let pos = get_light_source_pos_from_config();
- let text = format!("Light: {:.2} rad", pos);
- let label = Label::new_with_family(font_system, &text, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
- label.w + 2.0 * padding
+ // An empty circle with the bar's own thickness as its diameter; the
+ // radians value lives in the module's menu, not the strip.
+ crate::read_status_height_from_config()
}
fn render(
&self,
x: f32,
- _w: f32,
+ w: f32,
_stats: &Option<SystemStats>,
_viewport: &str,
_layout: &str,
_title: &str,
- font_system: &mut FontSystem,
- font_family: &str,
- font_size: f32,
+ _font_system: &mut FontSystem,
+ _font_family: &str,
+ _font_size: f32,
normal_color: [f32; 4],
bar_h: f32,
_scale_factor: f64,
- text_prims: &mut Vec<crate::TextPrim>,
+ _text_prims: &mut Vec<crate::TextPrim>,
_rects: &mut Vec<RectWidget>,
_overlay_rects: &mut Vec<RectWidget>,
_viewport_bounds: &mut Vec<ViewportBounds>,
@@ -887,12 +890,28 @@ impl StatusModule for LightSourceModule {
_tray_item_bounds: &mut Vec<TrayIconBounds>,
_box_bg_color: Option<[f32; 4]>,
_status_box_radius: f32,
- _rounded_boxes: &mut Vec<RoundedBox>,
- padding: f32,
+ rounded_boxes: &mut Vec<RoundedBox>,
+ _padding: f32,
) {
- let pos = get_light_source_pos_from_config();
- let text = format!("Light: {:.2} rad", pos);
- let label = Label::new_with_family(font_system, &text, font_size, normal_color, font_family);
- crate::draw_label(text_prims, label, x + padding, centered_text_y(bar_h, font_size));
+ 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,
+ ];
+ 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],
+ corners: (true, true, true, true),
+ border: Some((ring, 1.5)),
+ });
}
}