Skip to main content

tui_lipan/widgets/
status_bar.rs

1//! Status bar widget.
2
3use std::sync::Arc;
4
5use crate::core::element::{Element, IntoElement};
6use crate::style::{Justify, Length, Padding, Style, Theme};
7use crate::widgets::status_bar_layout::StatusBarLayout;
8use crate::widgets::{HStack, Spacer, Spinner, SpinnerSpeed, SpinnerStyle, ThemeProvider};
9
10/// A status bar widget.
11#[derive(Clone)]
12pub struct StatusBar {
13    left: Vec<Element>,
14    center: Vec<Element>,
15    right: Vec<Element>,
16    style: Style,
17    left_style: Style,
18    center_style: Style,
19    right_style: Style,
20    padding: Padding,
21    gap: u16,
22    width: Length,
23    height: Length,
24    reserve_center_space: bool,
25    loading: bool,
26    loading_label: Arc<str>,
27    loading_style: Style,
28    loading_spinner_style: SpinnerStyle,
29    loading_spinner_speed: SpinnerSpeed,
30}
31
32impl Default for StatusBar {
33    fn default() -> Self {
34        Self {
35            left: Vec::new(),
36            center: Vec::new(),
37            right: Vec::new(),
38            style: Style::default(),
39            left_style: Style::default(),
40            center_style: Style::default(),
41            right_style: Style::default(),
42            padding: (0, 1).into(),
43            gap: 1,
44            width: Length::Flex(1),
45            height: Length::Px(1),
46            reserve_center_space: false,
47            loading: false,
48            loading_label: "Loading".into(),
49            loading_style: Style::default(),
50            loading_spinner_style: SpinnerStyle::Dots,
51            loading_spinner_speed: SpinnerSpeed::Normal,
52        }
53    }
54}
55
56impl StatusBar {
57    /// Create a new status bar.
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// Add a child to the left section (alias for `left`).
63    pub fn child(self, item: impl IntoElement) -> Self {
64        self.left(item)
65    }
66
67    /// Add an item to the left section.
68    pub fn left(mut self, item: impl IntoElement) -> Self {
69        self.left.push(item.into());
70        self
71    }
72
73    /// Add an item to the center section.
74    pub fn center(mut self, item: impl IntoElement) -> Self {
75        self.center.push(item.into());
76        self
77    }
78
79    /// Add an item to the right section.
80    pub fn right(mut self, item: impl IntoElement) -> Self {
81        self.right.push(item.into());
82        self
83    }
84
85    /// Set base style.
86    pub fn style(mut self, style: Style) -> Self {
87        self.style = style;
88        self
89    }
90
91    /// Set style for the left section.
92    pub fn left_style(mut self, style: Style) -> Self {
93        self.left_style = style;
94        self
95    }
96
97    /// Set style for the center section.
98    pub fn center_style(mut self, style: Style) -> Self {
99        self.center_style = style;
100        self
101    }
102
103    /// Set style for the right section.
104    pub fn right_style(mut self, style: Style) -> Self {
105        self.right_style = style;
106        self
107    }
108
109    /// Set padding.
110    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
111        self.padding = padding.into();
112        self
113    }
114
115    /// Set gap between items.
116    pub fn gap(mut self, gap: u16) -> Self {
117        self.gap = gap;
118        self
119    }
120
121    /// Set width.
122    pub fn width(mut self, width: Length) -> Self {
123        self.width = width;
124        self
125    }
126
127    /// Set height.
128    pub fn height(mut self, height: Length) -> Self {
129        self.height = height;
130        self
131    }
132
133    /// Keep a reserved center lane even when no center items are set.
134    ///
135    /// When `false` (default), the bar collapses to left + right sections if the
136    /// center section is empty.
137    pub fn reserve_center_space(mut self, reserve: bool) -> Self {
138        self.reserve_center_space = reserve;
139        self
140    }
141
142    /// Show a loading indicator in the status bar.
143    pub fn loading(mut self, loading: bool) -> Self {
144        self.loading = loading;
145        self
146    }
147
148    /// Set loading label.
149    pub fn loading_label(mut self, label: impl Into<Arc<str>>) -> Self {
150        self.loading_label = label.into();
151        self
152    }
153
154    /// Set loading style.
155    pub fn loading_style(mut self, style: Style) -> Self {
156        self.loading_style = style;
157        self
158    }
159
160    /// Set loading spinner style.
161    pub fn loading_spinner_style(mut self, style: SpinnerStyle) -> Self {
162        self.loading_spinner_style = style;
163        self
164    }
165
166    /// Set loading spinner speed.
167    pub fn loading_spinner_speed(mut self, speed: SpinnerSpeed) -> Self {
168        self.loading_spinner_speed = speed;
169        self
170    }
171}
172
173impl From<StatusBar> for Element {
174    fn from(bar: StatusBar) -> Self {
175        let left_items = bar.left;
176        let center_items = bar.center;
177        let mut right_items = bar.right;
178
179        if bar.loading {
180            let spinner = Spinner::new()
181                .spinner_style(bar.loading_spinner_style)
182                .speed(bar.loading_spinner_speed)
183                .style(bar.loading_style)
184                .label(bar.loading_label)
185                .label_style(bar.loading_style);
186            right_items.push(spinner.into());
187        }
188
189        let center_has_content = !center_items.is_empty();
190        let left_has_content = !left_items.is_empty();
191        let right_has_content = !right_items.is_empty();
192
193        let left_theme_style = bar.style.patch(bar.left_style);
194        let center_theme_style = bar.style.patch(bar.center_style);
195        let right_theme_style = bar.style.patch(bar.right_style);
196
197        let content: Element = if center_has_content || bar.reserve_center_space {
198            StatusBarLayout {
199                left: Box::new(build_section(
200                    left_items,
201                    bar.gap,
202                    Justify::Start,
203                    left_theme_style,
204                )),
205                center: Box::new(build_section(
206                    center_items,
207                    bar.gap,
208                    Justify::Center,
209                    center_theme_style,
210                )),
211                right: Box::new(build_section(
212                    right_items,
213                    bar.gap,
214                    Justify::End,
215                    right_theme_style,
216                )),
217                style: bar.style,
218                padding: bar.padding,
219                gap: bar.gap,
220                width: bar.width,
221                height: bar.height,
222            }
223            .into()
224        } else {
225            // Two-lane layout: no center reservation when empty.
226            let left_section = build_section(left_items, bar.gap, Justify::Start, left_theme_style);
227            let right_section =
228                build_section(right_items, bar.gap, Justify::End, right_theme_style);
229
230            let base = HStack::new()
231                .height(bar.height)
232                .width(bar.width)
233                .style(bar.style)
234                .padding(bar.padding);
235
236            match (left_has_content, right_has_content) {
237                (true, true) => base
238                    .child(left_section)
239                    .child(Spacer::new())
240                    .child(right_section)
241                    .into(),
242                (true, false) => base.child(left_section).into(),
243                (false, true) => base.child(Spacer::new()).child(right_section).into(),
244                (false, false) => base.into(),
245            }
246        };
247
248        content
249    }
250}
251
252fn build_section(items: Vec<Element>, gap: u16, justify: Justify, style: Style) -> Element {
253    let mut section = HStack::new()
254        .width(Length::Auto)
255        .gap(gap)
256        .justify(justify)
257        .style(style);
258
259    for item in items {
260        section = section.child(item);
261    }
262
263    let local_theme = Theme {
264        primary: style,
265        ..Theme::default()
266    };
267
268    ThemeProvider::new(local_theme).child(section).into()
269}