Skip to main content

ratatui_toolkit/statusbar/
mod.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Color, Modifier, Style},
5    text::{Line, Span},
6    widgets::Widget,
7};
8
9/// A status bar widget typically displayed at the bottom of the screen
10pub struct StatusBar<'a> {
11    /// Left-aligned items
12    left_items: Vec<StatusItem<'a>>,
13    /// Center-aligned items
14    center_items: Vec<StatusItem<'a>>,
15    /// Right-aligned items
16    right_items: Vec<StatusItem<'a>>,
17    /// Background style for the status bar
18    style: Style,
19}
20
21/// An item in the status bar
22#[derive(Clone)]
23pub struct StatusItem<'a> {
24    /// The text content
25    text: String,
26    /// The style for this item
27    style: Style,
28    /// Optional separator after this item
29    separator: Option<&'a str>,
30}
31
32impl<'a> StatusItem<'a> {
33    /// Create a new status item
34    pub fn new(text: impl Into<String>) -> Self {
35        Self {
36            text: text.into(),
37            style: Style::default().fg(Color::White),
38            separator: Some(" │ "),
39        }
40    }
41
42    /// Set the style for this item
43    pub fn style(mut self, style: Style) -> Self {
44        self.style = style;
45        self
46    }
47
48    /// Set the separator (default is " │ ")
49    pub fn separator(mut self, separator: Option<&'a str>) -> Self {
50        self.separator = separator;
51        self
52    }
53
54    /// Create a styled item with a specific color
55    pub fn colored(text: impl Into<String>, color: Color) -> Self {
56        Self::new(text).style(Style::default().fg(color))
57    }
58
59    /// Create a bold item
60    pub fn bold(text: impl Into<String>) -> Self {
61        Self::new(text).style(Style::default().add_modifier(Modifier::BOLD))
62    }
63
64    /// Create a dimmed item
65    pub fn dimmed(text: impl Into<String>) -> Self {
66        Self::new(text).style(Style::default().fg(Color::DarkGray))
67    }
68}
69
70impl<'a> StatusBar<'a> {
71    /// Create a new status bar
72    pub fn new() -> Self {
73        Self {
74            left_items: Vec::new(),
75            center_items: Vec::new(),
76            right_items: Vec::new(),
77            style: Style::default().bg(Color::DarkGray).fg(Color::White),
78        }
79    }
80
81    /// Set the background style
82    pub fn style(mut self, style: Style) -> Self {
83        self.style = style;
84        self
85    }
86
87    /// Add items to the left section
88    pub fn left(mut self, items: Vec<StatusItem<'a>>) -> Self {
89        self.left_items = items;
90        self
91    }
92
93    /// Add items to the center section
94    pub fn center(mut self, items: Vec<StatusItem<'a>>) -> Self {
95        self.center_items = items;
96        self
97    }
98
99    /// Add items to the right section
100    pub fn right(mut self, items: Vec<StatusItem<'a>>) -> Self {
101        self.right_items = items;
102        self
103    }
104
105    /// Add a single item to the left
106    pub fn add_left(mut self, item: StatusItem<'a>) -> Self {
107        self.left_items.push(item);
108        self
109    }
110
111    /// Add a single item to the center
112    pub fn add_center(mut self, item: StatusItem<'a>) -> Self {
113        self.center_items.push(item);
114        self
115    }
116
117    /// Add a single item to the right
118    pub fn add_right(mut self, item: StatusItem<'a>) -> Self {
119        self.right_items.push(item);
120        self
121    }
122
123    /// Build spans from items
124    fn build_spans(&self, items: &[StatusItem<'a>]) -> Vec<Span<'_>> {
125        let mut spans = Vec::new();
126        for (i, item) in items.iter().enumerate() {
127            spans.push(Span::styled(item.text.clone(), item.style));
128            // Add separator if not the last item and separator is set
129            if i < items.len() - 1 {
130                if let Some(sep) = item.separator {
131                    spans.push(Span::styled(sep, Style::default().fg(Color::DarkGray)));
132                }
133            }
134        }
135        spans
136    }
137}
138
139impl Default for StatusBar<'_> {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl Widget for StatusBar<'_> {
146    fn render(self, area: Rect, buf: &mut Buffer) {
147        if area.height == 0 || area.width == 0 {
148            return;
149        }
150
151        // Fill the background
152        for x in area.x..area.x + area.width {
153            buf[(x, area.y)].set_style(self.style);
154        }
155
156        // Build left section
157        let left_spans = self.build_spans(&self.left_items);
158        if !left_spans.is_empty() {
159            let left_line = Line::from(left_spans);
160            buf.set_line(area.x + 1, area.y, &left_line, area.width);
161        }
162
163        // Build center section
164        let center_spans = self.build_spans(&self.center_items);
165        if !center_spans.is_empty() {
166            let center_line = Line::from(center_spans);
167            let center_width = center_line.width() as u16;
168            let center_x = area.x + (area.width.saturating_sub(center_width)) / 2;
169            buf.set_line(center_x, area.y, &center_line, area.width);
170        }
171
172        // Build right section
173        let right_spans = self.build_spans(&self.right_items);
174        if !right_spans.is_empty() {
175            let right_line = Line::from(right_spans);
176            let right_width = right_line.width() as u16;
177            let right_x = area.x + area.width.saturating_sub(right_width + 1);
178            buf.set_line(right_x, area.y, &right_line, area.width);
179        }
180    }
181}
182
183/// Builder methods for common status bar patterns
184impl<'a> StatusBar<'a> {
185    /// Create a status bar with a simple message
186    pub fn with_message(message: impl Into<String>) -> Self {
187        Self::new().add_left(StatusItem::new(message))
188    }
189
190    /// Create a status bar showing mode and position (vim-like)
191    pub fn vim_style(mode: impl Into<String>, line: usize, col: usize, total: usize) -> Self {
192        Self::new()
193            .add_left(StatusItem::bold(mode).separator(None))
194            .add_right(StatusItem::dimmed(format!("{}:{}", line, col)))
195            .add_right(
196                StatusItem::dimmed(format!("{:.0}%", (line as f64 / total as f64) * 100.0))
197                    .separator(None),
198            )
199    }
200
201    /// Create a status bar with file information
202    pub fn file_info(filename: impl Into<String>, modified: bool, readonly: bool) -> Self {
203        let mut bar = Self::new().add_left(StatusItem::new(filename));
204
205        if modified {
206            bar = bar.add_left(StatusItem::colored("[+]", Color::Yellow));
207        }
208
209        if readonly {
210            bar = bar.add_left(StatusItem::colored("[RO]", Color::Red));
211        }
212
213        bar
214    }
215}