ratatui_toolkit/
statusbar.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Modifier, Style},
5 text::{Line, Span},
6 widgets::Widget,
7};
8
9pub struct StatusBar<'a> {
11 left_items: Vec<StatusItem<'a>>,
13 center_items: Vec<StatusItem<'a>>,
15 right_items: Vec<StatusItem<'a>>,
17 style: Style,
19}
20
21#[derive(Clone)]
23pub struct StatusItem<'a> {
24 text: String,
26 style: Style,
28 separator: Option<&'a str>,
30}
31
32impl<'a> StatusItem<'a> {
33 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 pub fn style(mut self, style: Style) -> Self {
44 self.style = style;
45 self
46 }
47
48 pub fn separator(mut self, separator: Option<&'a str>) -> Self {
50 self.separator = separator;
51 self
52 }
53
54 pub fn colored(text: impl Into<String>, color: Color) -> Self {
56 Self::new(text).style(Style::default().fg(color))
57 }
58
59 pub fn bold(text: impl Into<String>) -> Self {
61 Self::new(text).style(Style::default().add_modifier(Modifier::BOLD))
62 }
63
64 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 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 pub fn style(mut self, style: Style) -> Self {
83 self.style = style;
84 self
85 }
86
87 pub fn left(mut self, items: Vec<StatusItem<'a>>) -> Self {
89 self.left_items = items;
90 self
91 }
92
93 pub fn center(mut self, items: Vec<StatusItem<'a>>) -> Self {
95 self.center_items = items;
96 self
97 }
98
99 pub fn right(mut self, items: Vec<StatusItem<'a>>) -> Self {
101 self.right_items = items;
102 self
103 }
104
105 pub fn add_left(mut self, item: StatusItem<'a>) -> Self {
107 self.left_items.push(item);
108 self
109 }
110
111 pub fn add_center(mut self, item: StatusItem<'a>) -> Self {
113 self.center_items.push(item);
114 self
115 }
116
117 pub fn add_right(mut self, item: StatusItem<'a>) -> Self {
119 self.right_items.push(item);
120 self
121 }
122
123 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 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 for x in area.x..area.x + area.width {
153 buf[(x, area.y)].set_style(self.style);
154 }
155
156 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 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, ¢er_line, area.width);
170 }
171
172 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
183impl<'a> StatusBar<'a> {
185 pub fn with_message(message: impl Into<String>) -> Self {
187 Self::new().add_left(StatusItem::new(message))
188 }
189
190 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 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}