gpui_component/status_bar.rs
1use gpui::{
2 AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,
3 prelude::FluentBuilder as _,
4};
5use smallvec::SmallVec;
6
7use crate::{ActiveTheme, StyledExt, h_flex};
8
9/// A horizontal status bar, usually placed at the bottom of a window or pane.
10///
11/// It is split into three regions — `left`, `center`, and `right`. This mirrors
12/// the status bars found in native UI frameworks (Windows `StatusStrip`, WPF
13/// `StatusBar`, macOS `NSStatusBar`): a container that holds a row of items
14/// aligned to either end.
15///
16/// Each region accepts any [`IntoElement`], so a string, an [`Icon`](crate::Icon),
17/// a ghost `Button`, a vertical `Separator`, a custom layout, etc. can be passed
18/// directly. Use a plain string for a non-interactive label.
19///
20/// `left` and `right` pin items to each end. `child`/`children` add to the
21/// center region, whose alignment follows the pinned ends: centered with both
22/// `left` and `right`, end-aligned with only `left`, and start-aligned
23/// otherwise (only `right`, or neither — like a plain container).
24///
25/// ```
26/// use gpui_kit::component::status_bar::StatusBar;
27///
28/// let _ = StatusBar::new().left("Ln 1, Col 1").right("UTF-8");
29/// ```
30#[derive(IntoElement)]
31pub struct StatusBar {
32 style: StyleRefinement,
33 left: SmallVec<[AnyElement; 1]>,
34 right: SmallVec<[AnyElement; 1]>,
35 children: SmallVec<[AnyElement; 1]>,
36}
37
38impl StatusBar {
39 /// Create a new, empty [`StatusBar`].
40 pub fn new() -> Self {
41 Self {
42 style: StyleRefinement::default(),
43 left: SmallVec::new(),
44 right: SmallVec::new(),
45 children: SmallVec::new(),
46 }
47 }
48
49 /// Append an element to the left region. Call multiple times to add more.
50 pub fn left(mut self, child: impl IntoElement) -> Self {
51 self.left.push(child.into_any_element());
52 self
53 }
54
55 /// Append an element to the right region. Call multiple times to add more.
56 pub fn right(mut self, child: impl IntoElement) -> Self {
57 self.right.push(child.into_any_element());
58 self
59 }
60}
61
62/// `child` / `children` add to the center region, so a `StatusBar` without
63/// `left`/`right` items behaves like a plain container.
64impl ParentElement for StatusBar {
65 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
66 self.children.extend(elements);
67 }
68}
69
70impl Styled for StatusBar {
71 fn style(&mut self) -> &mut StyleRefinement {
72 &mut self.style
73 }
74}
75
76impl RenderOnce for StatusBar {
77 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
78 // The center aligns by which ends are pinned: centered with both left
79 // and right, end-aligned with only left, otherwise start-aligned (only
80 // right, or neither) — so a bar with just `child`s reads like a container.
81 let has_left = !self.left.is_empty();
82 let has_right = !self.right.is_empty();
83 let region = || h_flex().overflow_hidden().items_center().gap_2();
84
85 h_flex()
86 .items_center()
87 .gap_2()
88 .py_1()
89 .px_2()
90 .border_t_1()
91 .border_color(cx.theme().status_bar_border)
92 .bg(cx.theme().tokens.status_bar)
93 .text_xs()
94 .text_color(cx.theme().muted_foreground)
95 .refine_style(&self.style)
96 .when(has_left, |this| this.child(region().children(self.left)))
97 .child(
98 region()
99 .flex_1()
100 .when(has_left && has_right, |this| this.justify_center())
101 .when(has_left && !has_right, |this| this.justify_end())
102 .children(self.children),
103 )
104 .when(has_right, |this| this.child(region().children(self.right)))
105 }
106}