Skip to main content

flywheel/widget/
status_bar.rs

1//! Status Bar Widget: Three-section status bar.
2//!
3//! A horizontal status bar with left, center, and right sections.
4//! Commonly used at the top or bottom of the terminal.
5
6use crate::actor::InputEvent;
7use crate::buffer::{Buffer, Cell, Rgb};
8use crate::layout::Rect;
9use super::traits::Widget;
10
11/// Configuration for the status bar widget.
12#[derive(Debug, Clone)]
13pub struct StatusBarConfig {
14    /// Background color.
15    pub bg: Rgb,
16    /// Left section text color.
17    pub left_fg: Rgb,
18    /// Center section text color.
19    pub center_fg: Rgb,
20    /// Right section text color.
21    pub right_fg: Rgb,
22}
23
24impl Default for StatusBarConfig {
25    fn default() -> Self {
26        Self {
27            bg: Rgb::new(40, 40, 40),
28            left_fg: Rgb::WHITE,
29            center_fg: Rgb::new(150, 150, 150),
30            right_fg: Rgb::new(100, 200, 100),
31        }
32    }
33}
34
35/// A three-section status bar (left, center, right).
36#[derive(Debug)]
37pub struct StatusBar {
38    /// Left section content.
39    left: String,
40    /// Center section content.
41    center: String,
42    /// Right section content.
43    right: String,
44    /// Widget bounds.
45    bounds: Rect,
46    /// Configuration.
47    config: StatusBarConfig,
48    /// Needs redraw flag.
49    dirty: bool,
50}
51
52impl StatusBar {
53    /// Create a new status bar with the given bounds.
54    pub fn new(bounds: Rect) -> Self {
55        Self {
56            left: String::new(),
57            center: String::new(),
58            right: String::new(),
59            bounds,
60            config: StatusBarConfig::default(),
61            dirty: true,
62        }
63    }
64
65    /// Create a new status bar with custom configuration.
66    pub const fn with_config(bounds: Rect, config: StatusBarConfig) -> Self {
67        Self {
68            left: String::new(),
69            center: String::new(),
70            right: String::new(),
71            bounds,
72            config,
73            dirty: true,
74        }
75    }
76
77    /// Set the left section content.
78    pub fn set_left(&mut self, text: impl Into<String>) {
79        self.left = text.into();
80        self.dirty = true;
81    }
82
83    /// Set the center section content.
84    pub fn set_center(&mut self, text: impl Into<String>) {
85        self.center = text.into();
86        self.dirty = true;
87    }
88
89    /// Set the right section content.
90    pub fn set_right(&mut self, text: impl Into<String>) {
91        self.right = text.into();
92        self.dirty = true;
93    }
94
95    /// Set all sections at once.
96    pub fn set_all(&mut self, left: impl Into<String>, center: impl Into<String>, right: impl Into<String>) {
97        self.left = left.into();
98        self.center = center.into();
99        self.right = right.into();
100        self.dirty = true;
101    }
102
103    /// Get the left section content.
104    pub fn left(&self) -> &str {
105        &self.left
106    }
107
108    /// Get the center section content.
109    pub fn center(&self) -> &str {
110        &self.center
111    }
112
113    /// Get the right section content.
114    pub fn right(&self) -> &str {
115        &self.right
116    }
117}
118
119impl Widget for StatusBar {
120    fn bounds(&self) -> Rect {
121        self.bounds
122    }
123
124    fn set_bounds(&mut self, bounds: Rect) {
125        self.bounds = bounds;
126        self.dirty = true;
127    }
128
129    fn render(&self, buffer: &mut Buffer) {
130        let x = self.bounds.x;
131        let y = self.bounds.y;
132        let width = self.bounds.width as usize;
133
134        // Clear the line with background
135        for i in 0..self.bounds.width {
136            buffer.set(x + i, y, Cell::new(' ').with_bg(self.config.bg));
137        }
138
139        // Draw left section (left-aligned)
140        let left_chars: Vec<char> = self.left.chars().collect();
141        for (i, &c) in left_chars.iter().take(width / 3).enumerate() {
142            #[allow(clippy::cast_possible_truncation)]
143            let px = x + i as u16;
144            buffer.set(px, y, Cell::new(c)
145                .with_fg(self.config.left_fg)
146                .with_bg(self.config.bg));
147        }
148
149        // Draw center section (centered)
150        let center_chars: Vec<char> = self.center.chars().collect();
151        let center_len = center_chars.len().min(width / 3);
152        #[allow(clippy::cast_possible_truncation)]
153        let center_start = x + ((width - center_len) / 2) as u16;
154        for (i, &c) in center_chars.iter().take(center_len).enumerate() {
155            #[allow(clippy::cast_possible_truncation)]
156            let px = center_start + i as u16;
157            buffer.set(px, y, Cell::new(c)
158                .with_fg(self.config.center_fg)
159                .with_bg(self.config.bg));
160        }
161
162        // Draw right section (right-aligned)
163        let right_chars: Vec<char> = self.right.chars().collect();
164        let right_len = right_chars.len().min(width / 3);
165        #[allow(clippy::cast_possible_truncation)]
166        let right_start = x + (width - right_len) as u16;
167        for (i, &c) in right_chars.iter().take(right_len).enumerate() {
168            #[allow(clippy::cast_possible_truncation)]
169            let px = right_start + i as u16;
170            buffer.set(px, y, Cell::new(c)
171                .with_fg(self.config.right_fg)
172                .with_bg(self.config.bg));
173        }
174    }
175
176    fn handle_input(&mut self, _event: &InputEvent) -> bool {
177        // Status bar doesn't handle input
178        false
179    }
180
181    fn needs_redraw(&self) -> bool {
182        self.dirty
183    }
184
185    fn clear_redraw(&mut self) {
186        self.dirty = false;
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_status_bar_basic() {
196        let mut bar = StatusBar::new(Rect::new(0, 0, 80, 1));
197        
198        bar.set_left("Left");
199        bar.set_center("Center");
200        bar.set_right("Right");
201        
202        assert_eq!(bar.left(), "Left");
203        assert_eq!(bar.center(), "Center");
204        assert_eq!(bar.right(), "Right");
205    }
206
207    #[test]
208    fn test_status_bar_set_all() {
209        let mut bar = StatusBar::new(Rect::new(0, 0, 80, 1));
210        
211        bar.set_all("A", "B", "C");
212        
213        assert_eq!(bar.left(), "A");
214        assert_eq!(bar.center(), "B");
215        assert_eq!(bar.right(), "C");
216    }
217}