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

commit45af4526d1af2ef767698747b954ca5e14cf344d
parente9cae3e2d0
authorLucas Galante <[email protected]>
date2026-06-24 12:13
Refactor status bar modules into component-based structure using StatusModule trait

 src/main.rs    | 704 ++++++++++++++---------------------------------
 src/modules.rs | 840 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1038 insertions(+), 506 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index bf80d65..8389908 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,6 @@
+mod modules;
+use modules::{StatusModule, TagsModule, LayoutModule, TitleModule, ClockModule, BatteryModule, VolumeModule, BrightnessModule, MemoryModule, CpuModule, TrayModule};
+
 use std::collections::HashMap;
 use std::sync::Arc;
 use glyphon::{
@@ -5,67 +8,67 @@ use glyphon::{
 };
 use cce_ui::color;
 use cce_ui::widget::{
-    StyledLabel as Label, TextItem, Separator, Element,
+    TextItem, Separator, Element,
     MouseButton, ElementState, MouseScrollDelta, KeyEvent,
 };
 
 #[derive(Debug, Clone)]
-struct TrayPixmap {
-    width: i32,
-    height: i32,
-    pixels: Vec<u8>,
+pub struct TrayPixmap {
+    pub width: i32,
+    pub height: i32,
+    pub pixels: Vec<u8>,
 }
 
 #[allow(dead_code)]
 #[derive(Debug, Clone)]
-struct TrayItem {
-    id: String,
-    icon_name: Option<String>,
-    icon_theme_path: Option<String>,
-    pixmaps: Option<Vec<TrayPixmap>>,
-    title: Option<String>,
-    dbus_id: Option<String>,
+pub struct TrayItem {
+    pub id: String,
+    pub icon_name: Option<String>,
+    pub icon_theme_path: Option<String>,
+    pub pixmaps: Option<Vec<TrayPixmap>>,
+    pub title: Option<String>,
+    pub dbus_id: Option<String>,
 }
 
 #[derive(Debug, Clone)]
-struct TrayIconBounds {
-    id: String,
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
-    title: Option<String>,
-    dbus_id: Option<String>,
+pub struct TrayIconBounds {
+    pub id: String,
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+    pub title: Option<String>,
+    pub dbus_id: Option<String>,
 }
 
 #[derive(Debug, Clone)]
-struct TagBounds {
-    name: String,
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
+pub struct TagBounds {
+    pub name: String,
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
 }
 
 #[derive(Debug, Clone)]
-struct LayoutBounds {
-    x: f32,
-    y: f32,
-    w: f32,
-    h: f32,
+pub struct LayoutBounds {
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
 }
 
 #[derive(Debug, Clone)]
-struct SystemStats {
-    clock: String,
-    memory: String,
-    cpu: String,
-    battery: String,
-    battery_capacity: i32,
-    battery_charging: bool,
-    volume: String,
-    volume_muted: bool,
-    brightness: String,
+pub struct SystemStats {
+    pub clock: String,
+    pub memory: String,
+    pub cpu: String,
+    pub battery: String,
+    pub battery_capacity: i32,
+    pub battery_charging: bool,
+    pub volume: String,
+    pub volume_muted: bool,
+    pub brightness: String,
 }
 
 #[derive(Debug, Clone)]
@@ -79,7 +82,7 @@ enum CustomEvent {
     LayoutMenuClosed(u32),
 }
 
-fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32, font_family: &str) -> Buffer {
+pub(crate) fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32, font_family: &str) -> Buffer {
     let scale = cce_ui::scale::scale_factor();
     let mut font_size = size;
 
@@ -107,7 +110,7 @@ fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32, font_family: &st
     buf
 }
 
-fn parse_hex_to_rgba(hex: &str) -> Option<[f32; 4]> {
+pub(crate) fn parse_hex_to_rgba(hex: &str) -> Option<[f32; 4]> {
     let s = hex.trim_start_matches('#');
     if s.len() == 6 {
         let r = u8::from_str_radix(&s[0..2], 16).ok()? as f32 / 255.0;
@@ -119,7 +122,7 @@ fn parse_hex_to_rgba(hex: &str) -> Option<[f32; 4]> {
     }
 }
 
-fn parse_tags(pango: &str) -> Vec<([f32; 4], String)> {
+pub(crate) fn parse_tags(pango: &str) -> Vec<([f32; 4], String)> {
     let mut result = Vec::new();
     let mut remaining = pango;
     while let Some(start_span) = remaining.find("<span color='") {
@@ -227,16 +230,16 @@ fn read_brightness() -> Option<String> {
     None
 }
 
-struct RectWidget {
-    x: f32, y: f32, w: f32, h: f32,
-    color: [f32; 4],
+pub struct RectWidget {
+    pub x: f32, pub y: f32, pub w: f32, pub h: f32,
+    pub color: [f32; 4],
 }
 
-struct RoundedBox {
-    x: f32, y: f32, w: f32, h: f32,
-    radius: f32,
-    color: [f32; 4],
-    corners: (bool, bool, bool, bool),
+pub struct RoundedBox {
+    pub x: f32, pub y: f32, pub w: f32, pub h: f32,
+    pub radius: f32,
+    pub color: [f32; 4],
+    pub corners: (bool, bool, bool, bool),
 }
 
 
@@ -276,21 +279,17 @@ impl StatusApp {
     fn rebuild_layout(&mut self) {
         let font_family = read_status_font_from_config();
         let font_size = read_status_font_size_from_config();
-        let show_separators = read_status_separators_from_config();
+        let show_separators = false;
         let padding = read_status_padding_from_config();
         let separator_color = read_separator_color_from_config().unwrap_or(color::STATUS_ACCENT);
         let normal_color = read_normal_color_from_config().unwrap_or(color::TEXT_FG);
         let sw_logical = self.width as f32;
-        let sh_logical = self.height as f32;
-        let _s = 1.0f32;
         let bar_h = read_status_height_from_config();
-        eprintln!("[rebuild_layout] sw_logical={}, sh_logical={}, font_family={}, font_size={}", sw_logical, sh_logical, font_family, font_size);
 
         self.current_bg_color = read_bg_color_from_config().unwrap_or(color::STATUS_BG);
         if let Some(opacity) = cce_ui::color::read_opacity_if_configured() {
             self.current_bg_color[3] = opacity;
         }
-        eprintln!("[rebuild_layout] Using background color: {:?}", self.current_bg_color);
 
         self.rects.clear();
         self.overlay_rects.clear();
@@ -301,13 +300,11 @@ impl StatusApp {
         let box_bg_color = read_status_box_background_color_from_config();
         let status_box_radius = read_status_box_corner_radius_from_config();
 
-        // 1. Background (using StatusBar widget)
         self.status_bar.set_rect(0.0, 0.0, sw_logical, bar_h);
         self.status_bar.set_bg_color(self.current_bg_color);
         
         let show_underline = read_status_underline_from_config();
         if show_underline {
-            // 1b. Accent border at bottom of the status bar area
             self.rects.push(RectWidget {
                 x: 0.0, y: bar_h - 2.0, w: sw_logical, h: 2.0,
                 color: separator_color,
@@ -316,472 +313,172 @@ impl StatusApp {
 
         self.tag_bounds.clear();
         self.layout_bounds = None;
-        let mut left_x = 12.0;
-
-        // 2. Tags
-        let tags = parse_tags(&self.tags);
-        for (col, text) in tags {
-            let label_str = format!(" {} ", text);
-            let label = Label::new_with_family(&mut self.font_system, &label_str, font_size, col, &font_family);
-            let line_w = label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: left_x,
-                    y: 0.0,
-                    w: line_w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
-            }
-            self.tag_bounds.push(TagBounds {
-                name: text.clone(),
-                x: left_x,
-                y: 0.0,
-                w: line_w,
-                h: bar_h,
-            });
-            left_x += line_w + 4.0;
-        }
-
-        // Spacing/separator before Layout
-        if !self.layout.is_empty() {
-            if show_separators {
-                self.separators.push(Separator::new(
-                    left_x + padding,
-                    0.0,
-                    1.0,
-                    bar_h,
-                    separator_color,
-                ));
-            }
-            left_x += padding * 2.0;
-        }
-
-        // 3. Layout Mode
-        if !self.layout.is_empty() {
-            let label_str = self.layout.clone();
-            let label = Label::new_with_family(&mut self.font_system, &label_str, font_size, normal_color, &font_family);
-            let line_w = label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: left_x,
-                    y: 0.0,
-                    w: line_w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
-            }
-            eprintln!("[rebuild_layout] Layout Mode: '{}', x={}, y={}, w={}, h={}", self.layout, left_x, 0.0, line_w, bar_h);
-            self.layout_bounds = Some(LayoutBounds {
-                x: left_x,
-                y: 0.0,
-                w: line_w,
-                h: bar_h,
-            });
-            left_x += line_w;
-        }
-
-        // Spacing/separator before Title
-        if !self.title.is_empty() && self.title != "(none)" {
-            if show_separators {
-                self.separators.push(Separator::new(
-                    left_x + padding,
-                    0.0,
-                    1.0,
-                    bar_h,
-                    separator_color,
-                ));
-            }
-            left_x += padding * 2.0;
-        }
-
-        // 4. Focused Title (drawn as a Label)
-        if !self.title.is_empty() && self.title != "(none)" {
-            let mut display_title = self.title.clone();
-            if display_title.chars().count() > 40 {
-                display_title = display_title.chars().take(37).collect::<String>() + "...";
-            }
-            let label = Label::new_with_family(&mut self.font_system, &display_title, font_size, normal_color, &font_family);
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: left_x,
-                    y: 0.0,
-                    w: label.w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
-            }
-            label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
-        }
-        self.status_bar.set_text("");
 
-        // 5. Right Side Stats (CPU, Mem, Bat, Clock)
-        let mut right_x = sw_logical - 12.0;
-        if let Some(ref stats) = self.stats {
-            let clock_w = 270.0;
-            let battery_w = 80.0;
-            let volume_w = 80.0;
-            let brightness_w = 80.0;
-            let memory_w = 140.0;
-            let cpu_w = 90.0;
-
-            // Clock
-            let label = Label::new_with_family(&mut self.font_system, &stats.clock, font_size, normal_color, &font_family);
-            right_x -= clock_w;
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: right_x,
-                    y: 0.0,
-                    w: clock_w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
-            }
-            let draw_x = right_x + (clock_w - label.w) / 2.0; // centered
-            eprintln!("[rebuild_layout] Clock: x={}, w={}", draw_x, label.w);
-            label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
-
-            // Battery
-            if !stats.battery.is_empty() {
-                right_x -= padding * 2.0;
-                if show_separators {
-                    self.separators.push(Separator::new(
-                        right_x + padding,
-                        0.0,
-                        1.0,
-                        bar_h,
-                        separator_color,
-                    ));
-                }
-                let bat_color = if !stats.battery_charging && stats.battery_capacity > 10 {
-                    normal_color
-                } else {
-                    color::TEXT_ACCENT
-                };
-                let label = Label::new_with_family(&mut self.font_system, &stats.battery, font_size, bat_color, &font_family);
-                right_x -= battery_w;
-                if let Some(color) = box_bg_color {
-                    self.rounded_boxes.push(RoundedBox {
-                        x: right_x,
-                        y: 0.0,
-                        w: battery_w,
-                        h: bar_h,
-                        radius: status_box_radius,
-                        color,
-                        corners: (false, false, true, true),
-                    });
-                }
-                let draw_x = right_x + (battery_w - label.w) / 2.0; // centered
-                eprintln!("[rebuild_layout] Battery: x={}, w={}", draw_x, label.w);
-                label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
-            }
+        let left_modules: Vec<Box<dyn StatusModule>> = vec![
+            Box::new(TagsModule),
+            Box::new(LayoutModule),
+            Box::new(TitleModule),
+        ];
 
-            // Volume
-            if !stats.volume.is_empty() {
-                right_x -= padding * 2.0;
-                if show_separators {
-                    self.separators.push(Separator::new(
-                        right_x + padding,
-                        0.0,
-                        1.0,
-                        bar_h,
-                        separator_color,
-                    ));
-                }
-                let is_muted = stats.volume_muted;
-                let color_val = if is_muted {
-                    read_disabled_color_from_config().unwrap_or(color::TEXT_DIM)
-                } else {
-                    normal_color
-                };
-                let label = Label::new_with_family(&mut self.font_system, &stats.volume, font_size, color_val, &font_family)
-                    .with_strikethrough(is_muted);
-                right_x -= volume_w;
-                if let Some(color) = box_bg_color {
-                    self.rounded_boxes.push(RoundedBox {
-                        x: right_x,
-                        y: 0.0,
-                        w: volume_w,
-                        h: bar_h,
-                        radius: status_box_radius,
-                        color,
-                        corners: (false, false, true, true),
-                    });
-                }
-                let draw_x = right_x + (volume_w - label.w) / 2.0; // centered
-                let start_y = (bar_h - font_size * 1.4) / 2.0;
-                eprintln!("[rebuild_layout] Volume: x={}, w={}, is_muted={}", draw_x, label.w, is_muted);
-                if let Some((sx, sy, sw_rect, sh_rect, scol)) = label.strikethrough_rect(draw_x, start_y, self.scale_factor as f32) {
-                    eprintln!("[rebuild_layout] Strikethrough rect: sx={}, sy={}, sw={}, sh={}, scol={:?}", sx, sy, sw_rect, sh_rect, scol);
-                    self.overlay_rects.push(RectWidget {
-                        x: sx,
-                        y: sy,
-                        w: sw_rect,
-                        h: sh_rect,
-                        color: color::to_linear(scol),
-                    });
-                }
-                label.draw(&mut self.text_items, draw_x, start_y);
-            }
+        let right_modules: Vec<Box<dyn StatusModule>> = vec![
+            Box::new(TrayModule),
+            Box::new(CpuModule),
+            Box::new(MemoryModule),
+            Box::new(BrightnessModule),
+            Box::new(VolumeModule),
+            Box::new(BatteryModule),
+            Box::new(ClockModule),
+        ];
 
-            // Brightness
-            if !stats.brightness.is_empty() {
-                right_x -= padding * 2.0;
-                if show_separators {
+        let mut left_x = 12.0;
+        let mut is_first_left = true;
+        for module in &left_modules {
+            let w = module.width(
+                &self.stats,
+                &self.tags,
+                &self.layout,
+                &self.title,
+                &mut self.font_system,
+                &font_family,
+                font_size,
+                &self.tray_items,
+            );
+            if w > 0.0 {
+                if !is_first_left && show_separators {
                     self.separators.push(Separator::new(
-                        right_x + padding,
+                        left_x + padding,
                         0.0,
                         1.0,
                         bar_h,
                         separator_color,
                     ));
+                    left_x += padding * 2.0;
                 }
-                let label = Label::new_with_family(&mut self.font_system, &stats.brightness, font_size, normal_color, &font_family);
-                right_x -= brightness_w;
-                if let Some(color) = box_bg_color {
-                    self.rounded_boxes.push(RoundedBox {
-                        x: right_x,
-                        y: 0.0,
-                        w: brightness_w,
-                        h: bar_h,
-                        radius: status_box_radius,
-                        color,
-                        corners: (false, false, true, true),
-                    });
+                is_first_left = false;
+
+                if !module.has_custom_background() {
+                    if let Some(color) = box_bg_color {
+                        self.rounded_boxes.push(RoundedBox {
+                            x: left_x,
+                            y: 0.0,
+                            w,
+                            h: bar_h,
+                            radius: status_box_radius,
+                            color,
+                            corners: (false, false, true, true),
+                        });
+                    }
                 }
-                let draw_x = right_x + (brightness_w - label.w) / 2.0; // centered
-                eprintln!("[rebuild_layout] Brightness: x={}, w={}", draw_x, label.w);
-                label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
-            }
 
-            // Memory
-            right_x -= padding * 2.0;
-            if show_separators {
-                self.separators.push(Separator::new(
-                    right_x + padding,
-                    0.0,
-                    1.0,
-                    bar_h,
-                    separator_color,
-                ));
-            }
-            let label = Label::new_with_family(&mut self.font_system, &stats.memory, font_size, normal_color, &font_family);
-            right_x -= memory_w;
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: right_x,
-                    y: 0.0,
-                    w: memory_w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
-            }
-            let draw_x = right_x + (memory_w - label.w) / 2.0; // centered
-            eprintln!("[rebuild_layout] Memory: x={}, w={}", draw_x, label.w);
-            label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
-
-            // CPU
-            right_x -= padding * 2.0;
-            if show_separators {
-                self.separators.push(Separator::new(
-                    right_x + padding,
-                    0.0,
-                    1.0,
+                module.render(
+                    left_x,
+                    w,
+                    &self.stats,
+                    &self.tags,
+                    &self.layout,
+                    &self.title,
+                    &mut self.font_system,
+                    &font_family,
+                    font_size,
+                    normal_color,
                     bar_h,
-                    separator_color,
-                ));
-            }
-            let label = Label::new_with_family(&mut self.font_system, &stats.cpu, font_size, normal_color, &font_family);
-            right_x -= cpu_w;
-            if let Some(color) = box_bg_color {
-                self.rounded_boxes.push(RoundedBox {
-                    x: right_x,
-                    y: 0.0,
-                    w: cpu_w,
-                    h: bar_h,
-                    radius: status_box_radius,
-                    color,
-                    corners: (false, false, true, true),
-                });
+                    self.scale_factor,
+                    &mut self.text_items,
+                    &mut self.rects,
+                    &mut self.overlay_rects,
+                    &mut self.tag_bounds,
+                    &mut self.layout_bounds,
+                    &self.tray_items,
+                    &mut self.tray_item_bounds,
+                    box_bg_color,
+                    status_box_radius,
+                    &mut self.rounded_boxes,
+                );
+
+                left_x += w;
             }
-            let draw_x = right_x + (cpu_w - label.w) / 2.0; // centered
-            eprintln!("[rebuild_layout] CPU: x={}, w={}", draw_x, label.w);
-            label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
         }
 
-        // 5b. System Tray Icons (render to the left of the CPU/stats block)
+        let mut right_x = sw_logical - 12.0;
+        let mut is_first_right = true;
+        
         self.tray_item_bounds.clear();
-        if !self.tray_items.is_empty() {
-            right_x -= padding * 2.0; // Separator padding
-            if show_separators {
-                self.separators.push(Separator::new(
-                    right_x + padding,
-                    0.0,
-                    1.0,
-                    bar_h,
-                    separator_color,
-                ));
-            }
-            let mut sorted_tray: Vec<&TrayItem> = self.tray_items.values().collect();
-            sorted_tray.sort_by_key(|item| &item.id);
-
-            right_x -= 8.0; // Right margin for tray block to match visual padding of other modules
-            let len = sorted_tray.len();
-            for (idx, item) in sorted_tray.iter().rev().enumerate() {
-                let icon_size = 16.0;
-                right_x -= icon_size;
-                let x = right_x;
-                let y = (bar_h - icon_size) / 2.0;
-
-                // Record bounds for hit-testing
-                eprintln!("[rebuild_layout] Tray icon bounds: id={}, x={}, y={}, w={}, h={}", item.id, x, y, icon_size, icon_size);
-                self.tray_item_bounds.push(TrayIconBounds {
-                    id: item.id.clone(),
-                    x,
-                    y,
-                    w: icon_size,
-                    h: icon_size,
-                    title: item.title.clone(),
-                    dbus_id: item.dbus_id.clone(),
-                });
 
-                // Attempt to draw pixmap
-                let mut drawn_pixmap = false;
-                if let Some(ref pixmaps) = item.pixmaps {
-                    if !pixmaps.is_empty() {
-                        let target_pixel_width = (icon_size * self.scale_factor as f32) as i32;
-                        if let Some(pixmap) = pixmaps.iter().min_by_key(|p| (p.width - target_pixel_width).abs()) {
-                            if pixmap.width > 0 && pixmap.height > 0 {
-                                // Calculate average brightness of visible pixels to see if we need to recolor
-                                let mut total_brightness = 0.0;
-                                let mut visible_pixel_count = 0;
-                                for row in 0..pixmap.height {
-                                    for col in 0..pixmap.width {
-                                        let idx = ((row * pixmap.width + col) * 4) as usize;
-                                        if idx + 3 < pixmap.pixels.len() {
-                                            let a = pixmap.pixels[idx] as f32 / 255.0;
-                                            if a > 0.1 {
-                                                let r = pixmap.pixels[idx + 1] as f32 / 255.0;
-                                                let g = pixmap.pixels[idx + 2] as f32 / 255.0;
-                                                let b = pixmap.pixels[idx + 3] as f32 / 255.0;
-                                                total_brightness += (r + g + b) / 3.0;
-                                                visible_pixel_count += 1;
-                                            }
-                                        }
-                                    }
-                                }
-                                
-                                let avg_brightness = if visible_pixel_count > 0 {
-                                    total_brightness / visible_pixel_count as f32
-                                } else {
-                                    0.5
-                                };
-                                let recolor_light = avg_brightness < 0.35;
- 
-                                let mut draw_w = pixmap.width;
-                                let mut draw_h = pixmap.height;
-                                if draw_w > 48 {
-                                    draw_w = 48;
-                                    draw_h = 48;
-                                }
-                                let pixel_w = icon_size / draw_w as f32;
-                                let pixel_h = icon_size / draw_h as f32;
-                                for row in 0..draw_h {
-                                    for col in 0..draw_w {
-                                        let src_row = row * pixmap.height / draw_h;
-                                        let src_col = col * pixmap.width / draw_w;
-                                        let idx = ((src_row * pixmap.width + src_col) * 4) as usize;
-                                        if idx + 3 < pixmap.pixels.len() {
-                                            let a = pixmap.pixels[idx] as f32 / 255.0;
-                                            if a > 0.0 {
-                                                let mut r = pixmap.pixels[idx + 1] as f32 / 255.0;
-                                                let mut g = pixmap.pixels[idx + 2] as f32 / 255.0;
-                                                let mut b = pixmap.pixels[idx + 3] as f32 / 255.0;
-                                                
-                                                if recolor_light {
-                                                    let l = (r + g + b) / 3.0;
-                                                    let new_l = 0.85 + (1.0 - 0.85) * l;
-                                                    r = new_l;
-                                                    g = new_l;
-                                                    b = new_l;
-                                                }
-                                                
-                                                self.rects.push(RectWidget {
-                                                    x: x + col as f32 * pixel_w,
-                                                    y: y + row as f32 * pixel_h,
-                                                    w: pixel_w,
-                                                    h: pixel_h,
-                                                    color: [r, g, b, a],
-                                                });
-                                            }
-                                        }
-                                    }
-                                }
-                                drawn_pixmap = true;
-                            }
+        for module in right_modules.iter().rev() {
+            let w = module.width(
+                &self.stats,
+                &self.tags,
+                &self.layout,
+                &self.title,
+                &mut self.font_system,
+                &font_family,
+                font_size,
+                &self.tray_items,
+            );
+            if w > 0.0 {
+                if !is_first_right {
+                    if module.name() == "tray" {
+                        right_x -= 8.0;
+                    } else {
+                        right_x -= padding * 2.0;
+                        if show_separators {
+                            self.separators.push(Separator::new(
+                                right_x + padding,
+                                0.0,
+                                1.0,
+                                bar_h,
+                                separator_color,
+                            ));
                         }
                     }
                 }
-
-                if !drawn_pixmap {
-                    let symbol = if let Some(ref name) = item.icon_name {
-                        let name_lower = name.to_lowercase();
-                        if name_lower.contains("volume") || name_lower.contains("sound") || name_lower.contains("audio") {
-                            if name_lower.contains("mute") { "🔇" } else { "🔊" }
-                        } else if name_lower.contains("wifi") || name_lower.contains("network") || name_lower.contains("ethernet") {
-                            "📶"
-                        } else if name_lower.contains("battery") {
-                            "🔋"
-                        } else if name_lower.contains("bluetooth") {
-                            "á›’"
-                        } else if name_lower.contains("mail") || name_lower.contains("envelope") {
-                            "✉"
-                        } else if name_lower.contains("chat") || name_lower.contains("messenger") || name_lower.contains("discord") || name_lower.contains("slack") || name_lower.contains("telegram") {
-                            "💬"
-                        } else if name_lower.contains("steam") || name_lower.contains("game") {
-                            "🎮"
-                        } else if name_lower.contains("dropbox") {
-                            "📦"
-                        } else {
-                            "âš™"
-                        }
-                    } else {
-                        "âš™"
-                    };
-
-                    let buf = make_text_buffer(&mut self.font_system, symbol, font_size, &font_family);
-                    let scale = cce_ui::scale::scale_factor();
-                    let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0) / scale;
-                    let tx = x + (icon_size - tw) / 2.0;
-                    let ty = y + (icon_size - font_size * 1.4) / 2.0;
-                    self.text_items.push(TextItem {
-                        buffer: buf,
-                        x: tx,
-                        y: ty,
-                        color: glyphon::Color::rgb(
-                            (color::TEXT_ACCENT[0] * 255.0) as u8,
-                            (color::TEXT_ACCENT[1] * 255.0) as u8,
-                            (color::TEXT_ACCENT[2] * 255.0) as u8,
-                        ),
-                        bounds: None,
-                    });
+                is_first_right = false;
+
+                right_x -= w;
+
+                if !module.has_custom_background() {
+                    if let Some(color) = box_bg_color {
+                        self.rounded_boxes.push(RoundedBox {
+                            x: right_x,
+                            y: 0.0,
+                            w,
+                            h: bar_h,
+                            radius: status_box_radius,
+                            color,
+                            corners: (false, false, true, true),
+                        });
+                    }
                 }
 
-                if idx < len - 1 {
-                    right_x -= 8.0; // Gap between icons
+                module.render(
+                    right_x,
+                    w,
+                    &self.stats,
+                    &self.tags,
+                    &self.layout,
+                    &self.title,
+                    &mut self.font_system,
+                    &font_family,
+                    font_size,
+                    normal_color,
+                    bar_h,
+                    self.scale_factor,
+                    &mut self.text_items,
+                    &mut self.rects,
+                    &mut self.overlay_rects,
+                    &mut self.tag_bounds,
+                    &mut self.layout_bounds,
+                    &self.tray_items,
+                    &mut self.tray_item_bounds,
+                    box_bg_color,
+                    status_box_radius,
+                    &mut self.rounded_boxes,
+                );
+
+                if module.name() == "tray" {
+                    right_x -= 8.0;
                 }
             }
-            right_x -= 8.0; // Left margin for tray block to match visual padding of other modules
         }
 
         // 5c. Tooltip Rendering (if hovered)
@@ -2262,7 +1959,7 @@ fn read_normal_color_from_config() -> Option<[f32; 4]> {
     parse_srgb_color_from_key(&content, "status_normal_color")
 }
 
-fn read_disabled_color_from_config() -> Option<[f32; 4]> {
+pub(crate) fn read_disabled_color_from_config() -> Option<[f32; 4]> {
     let content = std::fs::read_to_string("/home/lsgalante/.config/cce/config.json").ok()?;
     parse_srgb_color_from_key(&content, "disabled_color")
 }
@@ -2302,11 +1999,6 @@ fn read_status_font_size_from_config() -> f32 {
     json_find_key(&val, "status_font_size").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(11.0)
 }
 
-fn read_status_separators_from_config() -> bool {
-    let content = std::fs::read_to_string("/home/lsgalante/.config/cce/config.json").unwrap_or_default();
-    let val = parse_json(&content);
-    json_find_key(&val, "status_separators").and_then(|v| v.as_bool()).unwrap_or(true)
-}
 
 fn read_status_underline_from_config() -> bool {
     let content = std::fs::read_to_string("/home/lsgalante/.config/cce/config.json").unwrap_or_default();
diff --git a/src/modules.rs b/src/modules.rs
new file mode 100644
index 0000000..d8aa251
--- /dev/null
+++ b/src/modules.rs
@@ -0,0 +1,840 @@
+use std::collections::HashMap;
+use glyphon::FontSystem;
+use cce_ui::color;
+use cce_ui::widget::{StyledLabel as Label, TextItem};
+
+use crate::{
+    RectWidget, RoundedBox, TagBounds, LayoutBounds, SystemStats, TrayItem,
+    TrayIconBounds, make_text_buffer, parse_tags,
+};
+
+pub trait StatusModule {
+    fn name(&self) -> &'static str;
+    
+    fn has_custom_background(&self) -> bool { false }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        tags: &str,
+        layout: &str,
+        title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        tray_items: &HashMap<String, TrayItem>,
+    ) -> f32;
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        tags: &str,
+        layout: &str,
+        title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        rects: &mut Vec<RectWidget>,
+        overlay_rects: &mut Vec<RectWidget>,
+        tag_bounds: &mut Vec<TagBounds>,
+        layout_bounds: &mut Option<LayoutBounds>,
+        tray_items: &HashMap<String, TrayItem>,
+        tray_item_bounds: &mut Vec<TrayIconBounds>,
+        box_bg_color: Option<[f32; 4]>,
+        status_box_radius: f32,
+        rounded_boxes: &mut Vec<RoundedBox>,
+    );
+}
+
+pub struct TagsModule;
+
+impl StatusModule for TagsModule {
+    fn name(&self) -> &'static str { "tags" }
+    
+    fn has_custom_background(&self) -> bool { true }
+
+    fn width(
+        &self,
+        _stats: &Option<SystemStats>,
+        tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        let tags_parsed = parse_tags(tags);
+        if tags_parsed.is_empty() {
+            0.0
+        } else {
+            let mut total_w = 0.0;
+            for (col, text) in &tags_parsed {
+                let label_str = format!(" {} ", text);
+                let label = Label::new_with_family(font_system, &label_str, font_size, *col, font_family);
+                total_w += label.w + 4.0;
+            }
+            total_w
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        _w: f32,
+        _stats: &Option<SystemStats>,
+        tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        _normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        box_bg_color: Option<[f32; 4]>,
+        status_box_radius: f32,
+        rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        let tags_parsed = parse_tags(tags);
+        let mut cur_x = x;
+        for (col, text) in tags_parsed {
+            let label_str = format!(" {} ", text);
+            let label = Label::new_with_family(font_system, &label_str, font_size, col, font_family);
+            let line_w = label.draw(text_items, cur_x, (bar_h - font_size * 1.4) / 2.0);
+            if let Some(color) = box_bg_color {
+                rounded_boxes.push(RoundedBox {
+                    x: cur_x,
+                    y: 0.0,
+                    w: line_w,
+                    h: bar_h,
+                    radius: status_box_radius,
+                    color,
+                    corners: (false, false, true, true),
+                });
+            }
+            tag_bounds.push(TagBounds {
+                name: text.clone(),
+                x: cur_x,
+                y: 0.0,
+                w: line_w,
+                h: bar_h,
+            });
+            cur_x += line_w + 4.0;
+        }
+    }
+}
+
+pub struct LayoutModule;
+
+impl StatusModule for LayoutModule {
+    fn name(&self) -> &'static str { "layout" }
+
+    fn width(
+        &self,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if layout.is_empty() {
+            0.0
+        } else {
+            let label = Label::new_with_family(font_system, layout, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+            label.w
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        _w: f32,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if !layout.is_empty() {
+            let label = Label::new_with_family(font_system, layout, font_size, normal_color, font_family);
+            let line_w = label.draw(text_items, x, (bar_h - font_size * 1.4) / 2.0);
+            *layout_bounds = Some(LayoutBounds {
+                x,
+                y: 0.0,
+                w: line_w,
+                h: bar_h,
+            });
+        }
+    }
+}
+
+pub struct TitleModule;
+
+impl StatusModule for TitleModule {
+    fn name(&self) -> &'static str { "title" }
+
+    fn width(
+        &self,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if title.is_empty() || title == "(none)" {
+            0.0
+        } else {
+            let mut display_title = title.to_string();
+            if display_title.chars().count() > 40 {
+                display_title = display_title.chars().take(37).collect::<String>() + "...";
+            }
+            let label = Label::new_with_family(font_system, &display_title, font_size, [0.0, 0.0, 0.0, 1.0], font_family);
+            label.w
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        _w: f32,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if !title.is_empty() && title != "(none)" {
+            let mut display_title = title.to_string();
+            if display_title.chars().count() > 40 {
+                display_title = display_title.chars().take(37).collect::<String>() + "...";
+            }
+            let label = Label::new_with_family(font_system, &display_title, font_size, normal_color, font_family);
+            label.draw(text_items, x, (bar_h - font_size * 1.4) / 2.0);
+        }
+    }
+}
+
+pub struct ClockModule;
+
+impl StatusModule for ClockModule {
+    fn name(&self) -> &'static str { "clock" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if stats.is_some() {
+            270.0
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            let label = Label::new_with_family(font_system, &s.clock, font_size, normal_color, font_family);
+            let draw_x = x + (w - label.w) / 2.0;
+            label.draw(text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
+        }
+    }
+}
+
+pub struct BatteryModule;
+
+impl StatusModule for BatteryModule {
+    fn name(&self) -> &'static str { "battery" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if let Some(ref s) = stats {
+            if !s.battery.is_empty() {
+                80.0
+            } else {
+                0.0
+            }
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            if !s.battery.is_empty() {
+                let bat_color = if !s.battery_charging && s.battery_capacity > 10 {
+                    normal_color
+                } else {
+                    color::TEXT_ACCENT
+                };
+                let label = Label::new_with_family(font_system, &s.battery, font_size, bat_color, font_family);
+                let draw_x = x + (w - label.w) / 2.0;
+                label.draw(text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
+            }
+        }
+    }
+}
+
+pub struct VolumeModule;
+
+impl StatusModule for VolumeModule {
+    fn name(&self) -> &'static str { "volume" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if let Some(ref s) = stats {
+            if !s.volume.is_empty() {
+                80.0
+            } else {
+                0.0
+            }
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            if !s.volume.is_empty() {
+                let is_muted = s.volume_muted;
+                let color_val = if is_muted {
+                    crate::read_disabled_color_from_config().unwrap_or(color::TEXT_DIM)
+                } else {
+                    normal_color
+                };
+                let label = Label::new_with_family(font_system, &s.volume, font_size, color_val, font_family)
+                    .with_strikethrough(is_muted);
+                let draw_x = x + (w - label.w) / 2.0;
+                let start_y = (bar_h - font_size * 1.4) / 2.0;
+                if let Some((sx, sy, sw_rect, sh_rect, scol)) = label.strikethrough_rect(draw_x, start_y, scale_factor as f32) {
+                    overlay_rects.push(RectWidget {
+                        x: sx,
+                        y: sy,
+                        w: sw_rect,
+                        h: sh_rect,
+                        color: color::to_linear(scol),
+                    });
+                }
+                label.draw(text_items, draw_x, start_y);
+            }
+        }
+    }
+}
+
+pub struct BrightnessModule;
+
+impl StatusModule for BrightnessModule {
+    fn name(&self) -> &'static str { "brightness" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if let Some(ref s) = stats {
+            if !s.brightness.is_empty() {
+                80.0
+            } else {
+                0.0
+            }
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            if !s.brightness.is_empty() {
+                let label = Label::new_with_family(font_system, &s.brightness, font_size, normal_color, font_family);
+                let draw_x = x + (w - label.w) / 2.0;
+                label.draw(text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
+            }
+        }
+    }
+}
+
+pub struct MemoryModule;
+
+impl StatusModule for MemoryModule {
+    fn name(&self) -> &'static str { "memory" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if stats.is_some() {
+            140.0
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            let label = Label::new_with_family(font_system, &s.memory, font_size, normal_color, font_family);
+            let draw_x = x + (w - label.w) / 2.0;
+            label.draw(text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
+        }
+    }
+}
+
+pub struct CpuModule;
+
+impl StatusModule for CpuModule {
+    fn name(&self) -> &'static str { "cpu" }
+
+    fn width(
+        &self,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        _tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if stats.is_some() {
+            90.0
+        } else {
+            0.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        w: f32,
+        stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        normal_color: [f32; 4],
+        bar_h: f32,
+        _scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        _rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        _tray_items: &HashMap<String, TrayItem>,
+        _tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if let Some(ref s) = stats {
+            let label = Label::new_with_family(font_system, &s.cpu, font_size, normal_color, font_family);
+            let draw_x = x + (w - label.w) / 2.0;
+            label.draw(text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
+        }
+    }
+}
+
+pub struct TrayModule;
+
+impl StatusModule for TrayModule {
+    fn name(&self) -> &'static str { "tray" }
+    
+    fn has_custom_background(&self) -> bool { true }
+
+    fn width(
+        &self,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        _font_system: &mut FontSystem,
+        _font_family: &str,
+        _font_size: f32,
+        tray_items: &HashMap<String, TrayItem>,
+    ) -> f32 {
+        if tray_items.is_empty() {
+            0.0
+        } else {
+            let len = tray_items.len();
+            (len as f32 * 16.0) + 8.0
+        }
+    }
+
+    fn render(
+        &self,
+        x: f32,
+        _w: f32,
+        _stats: &Option<SystemStats>,
+        _tags: &str,
+        _layout: &str,
+        _title: &str,
+        font_system: &mut FontSystem,
+        font_family: &str,
+        font_size: f32,
+        _normal_color: [f32; 4],
+        bar_h: f32,
+        scale_factor: f64,
+        text_items: &mut Vec<TextItem>,
+        rects: &mut Vec<RectWidget>,
+        _overlay_rects: &mut Vec<RectWidget>,
+        _tag_bounds: &mut Vec<TagBounds>,
+        _layout_bounds: &mut Option<LayoutBounds>,
+        tray_items: &HashMap<String, TrayItem>,
+        tray_item_bounds: &mut Vec<TrayIconBounds>,
+        _box_bg_color: Option<[f32; 4]>,
+        _status_box_radius: f32,
+        _rounded_boxes: &mut Vec<RoundedBox>,
+    ) {
+        if tray_items.is_empty() {
+            return;
+        }
+        let mut sorted_tray: Vec<&TrayItem> = tray_items.values().collect();
+        sorted_tray.sort_by_key(|item| &item.id);
+
+        let mut right_x = x + (sorted_tray.len() as f32 * 16.0) + 8.0;
+        right_x -= 8.0;
+
+        for item in sorted_tray.iter().rev() {
+            let icon_size = 16.0;
+            right_x -= icon_size;
+            let icon_x = right_x;
+            let icon_y = (bar_h - icon_size) / 2.0;
+
+            tray_item_bounds.push(TrayIconBounds {
+                id: item.id.clone(),
+                x: icon_x,
+                y: icon_y,
+                w: icon_size,
+                h: icon_size,
+                title: item.title.clone(),
+                dbus_id: item.dbus_id.clone(),
+            });
+
+            let mut drawn_pixmap = false;
+            if let Some(ref pixmaps) = item.pixmaps {
+                if !pixmaps.is_empty() {
+                    let target_pixel_width = (icon_size * scale_factor as f32) as i32;
+                    if let Some(pixmap) = pixmaps.iter().min_by_key(|p| (p.width - target_pixel_width).abs()) {
+                        if pixmap.width > 0 && pixmap.height > 0 {
+                            let mut total_brightness = 0.0;
+                            let mut visible_pixel_count = 0;
+                            for row in 0..pixmap.height {
+                                for col in 0..pixmap.width {
+                                    let idx = ((row * pixmap.width + col) * 4) as usize;
+                                    if idx + 3 < pixmap.pixels.len() {
+                                        let a = pixmap.pixels[idx] as f32 / 255.0;
+                                        if a > 0.1 {
+                                            let r = pixmap.pixels[idx + 1] as f32 / 255.0;
+                                            let g = pixmap.pixels[idx + 2] as f32 / 255.0;
+                                            let b = pixmap.pixels[idx + 3] as f32 / 255.0;
+                                            total_brightness += (r + g + b) / 3.0;
+                                            visible_pixel_count += 1;
+                                        }
+                                    }
+                                }
+                            }
+                            
+                            let avg_brightness = if visible_pixel_count > 0 {
+                                total_brightness / visible_pixel_count as f32
+                            } else {
+                                0.5
+                            };
+                            let recolor_light = avg_brightness < 0.35;
+
+                            let mut draw_w = pixmap.width;
+                            let mut draw_h = pixmap.height;
+                            if draw_w > 48 {
+                                draw_w = 48;
+                                draw_h = 48;
+                            }
+                            let pixel_w = icon_size / draw_w as f32;
+                            let pixel_h = icon_size / draw_h as f32;
+                            for row in 0..draw_h {
+                                for col in 0..draw_w {
+                                    let src_row = row * pixmap.height / draw_h;
+                                    let src_col = col * pixmap.width / draw_w;
+                                    let idx = ((src_row * pixmap.width + src_col) * 4) as usize;
+                                    if idx + 3 < pixmap.pixels.len() {
+                                        let a = pixmap.pixels[idx] as f32 / 255.0;
+                                        if a > 0.0 {
+                                            let mut r = pixmap.pixels[idx + 1] as f32 / 255.0;
+                                            let mut g = pixmap.pixels[idx + 2] as f32 / 255.0;
+                                            let mut b = pixmap.pixels[idx + 3] as f32 / 255.0;
+                                            
+                                            if recolor_light {
+                                                let l = (r + g + b) / 3.0;
+                                                let new_l = 0.85 + (1.0 - 0.85) * l;
+                                                r = new_l;
+                                                g = new_l;
+                                                b = new_l;
+                                            }
+                                            
+                                            rects.push(RectWidget {
+                                                x: icon_x + col as f32 * pixel_w,
+                                                y: icon_y + row as f32 * pixel_h,
+                                                w: pixel_w,
+                                                h: pixel_h,
+                                                color: [r, g, b, a],
+                                            });
+                                        }
+                                    }
+                                }
+                            }
+                            drawn_pixmap = true;
+                        }
+                    }
+                }
+            }
+
+            if !drawn_pixmap {
+                let symbol = if let Some(ref name) = item.icon_name {
+                    let name_lower = name.to_lowercase();
+                    if name_lower.contains("volume") || name_lower.contains("sound") || name_lower.contains("audio") {
+                        if name_lower.contains("mute") { "🔇" } else { "🔊" }
+                    } else if name_lower.contains("wifi") || name_lower.contains("network") || name_lower.contains("ethernet") {
+                        "📶"
+                    } else if name_lower.contains("battery") {
+                        "🔋"
+                    } else if name_lower.contains("bluetooth") {
+                        "á›’"
+                    } else if name_lower.contains("mail") || name_lower.contains("envelope") {
+                        "✉"
+                    } else if name_lower.contains("chat") || name_lower.contains("messenger") || name_lower.contains("discord") || name_lower.contains("slack") || name_lower.contains("telegram") {
+                        "💬"
+                    } else if name_lower.contains("steam") || name_lower.contains("game") {
+                        "🎮"
+                    } else if name_lower.contains("dropbox") {
+                        "📦"
+                    } else {
+                        "âš™"
+                    }
+                } else {
+                    "âš™"
+                };
+
+                let buf = make_text_buffer(font_system, symbol, font_size, font_family);
+                let scale = cce_ui::scale::scale_factor();
+                let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0) / scale;
+                let tx = icon_x + (icon_size - tw) / 2.0;
+                let ty = icon_y + (icon_size - font_size * 1.4) / 2.0;
+                text_items.push(TextItem {
+                    buffer: buf,
+                    x: tx,
+                    y: ty,
+                    color: glyphon::Color::rgb(
+                        (color::TEXT_ACCENT[0] * 255.0) as u8,
+                        (color::TEXT_ACCENT[1] * 255.0) as u8,
+                        (color::TEXT_ACCENT[2] * 255.0) as u8,
+                    ),
+                    bounds: None,
+                });
+            }
+        }
+    }
+}