rat_widget/
statusline_stacked.rs

1//! A status-line widget that can stack up indicators
2//! on the left and right end.
3//!
4//! If you use the constants SLANT_TL_BR and SLANT_BL_TR as
5//! separator you can do neo-vim a neovim style statusline.
6//!
7//! ```
8//!
9//! # use std::time::Duration;
10//! use ratatui_core::buffer::Buffer;
11//! # use ratatui_core::layout::Rect;
12//! # use ratatui_core::style::{Color, Style, Stylize};
13//! # use ratatui_core::text::Span;
14//! # use ratatui_core::widgets::Widget;
15//! use rat_widget::statusline_stacked::{StatusLineStacked, SLANT_BL_TR, SLANT_TL_BR};
16//!
17//! # let area = Rect::default();
18//! # let mut buf = Buffer::default();
19//! # let buf = &mut buf;
20//!
21//! let color_0 = Color::DarkGray;
22//! let color_1 = Color::Green;
23//! let color_3 = Color::Cyan;
24//! let color_4 = Color::DarkGray;
25//!
26//! StatusLineStacked::new()
27//!     .start(
28//!         Span::from(" STATUS-0 ")
29//!             .style(Style::new().fg(Color::Black).bg(color_0)),
30//!         Span::from(SLANT_TL_BR).style(Style::new().fg(color_0).bg(color_1)),
31//!     )
32//!     .start(
33//!         Span::from(" STATUS-1 ").style(Style::new().fg(Color::Black).bg(color_1)),
34//!         Span::from(SLANT_TL_BR).style(Style::new().fg(color_1)),
35//!     )
36//!     .center_margin(1)
37//!     .center("Some status message ...")
38//!     .end(
39//!         Span::from(format!("R[{:.0?} ", Duration::from_micros(25)))
40//!             .style(Style::new().fg(Color::Black).bg(color_3)),
41//!         Span::from(SLANT_BL_TR).style(Style::new().fg(color_3).bg(color_4)),
42//!     )
43//!     .end(
44//!         "",
45//!         Span::from(SLANT_BL_TR).style(Style::new().fg(color_4).bg(color_3)),
46//!     )
47//!     .end(
48//!         Span::from(format!("E[{:.0?}", Duration::from_micros(17)))
49//!             .style(Style::new().fg(Color::Black).bg(color_3)),
50//!         Span::from(SLANT_BL_TR).style(Style::new().fg(color_3).bg(color_4)),
51//!     )
52//!     .end(
53//!         "",
54//!         Span::from(SLANT_BL_TR).style(Style::new().fg(color_4).bg(color_3)),
55//!     )
56//!     .end("", Span::from(SLANT_BL_TR).style(Style::new().fg(color_4)))
57//!     .render(area, buf);
58//!
59//! ```
60//!
61use ratatui_core::buffer::Buffer;
62use ratatui_core::layout::Rect;
63use ratatui_core::style::Style;
64use ratatui_core::text::Line;
65use ratatui_core::widgets::Widget;
66use std::marker::PhantomData;
67
68/// Block cut at the diagonal.
69pub const SLANT_TL_BR: &str = "\u{e0b8}";
70/// Block cut at the diagonal.
71pub const SLANT_BL_TR: &str = "\u{e0ba}";
72
73/// Statusline with indicators on the left and right side.
74#[derive(Debug, Default, Clone)]
75pub struct StatusLineStacked<'a> {
76    style: Style,
77    left: Vec<(Line<'a>, Line<'a>)>,
78    center_margin: u16,
79    center: Line<'a>,
80    right: Vec<(Line<'a>, Line<'a>)>,
81    phantom: PhantomData<&'a ()>,
82}
83
84impl<'a> StatusLineStacked<'a> {
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Baseline style.
90    pub fn style(mut self, style: Style) -> Self {
91        self.style = style;
92        self
93    }
94
95    /// Add to the start group of status flags.
96    /// These stack from left to right.
97    pub fn start(mut self, text: impl Into<Line<'a>>, gap: impl Into<Line<'a>>) -> Self {
98        self.left.push((text.into(), gap.into()));
99        self
100    }
101
102    /// Add to the start group of status flags.
103    /// These stack from left to right.
104    pub fn start_bare(mut self, text: impl Into<Line<'a>>) -> Self {
105        self.left.push((text.into(), "".into()));
106        self
107    }
108
109    /// Margin around centered text.
110    pub fn center_margin(mut self, margin: u16) -> Self {
111        self.center_margin = margin;
112        self
113    }
114
115    /// Centered text.
116    pub fn center(mut self, text: impl Into<Line<'a>>) -> Self {
117        self.center = text.into();
118        self
119    }
120
121    /// Add to the end group of status flags.
122    /// These stack from right to left.
123    pub fn end(mut self, text: impl Into<Line<'a>>, gap: impl Into<Line<'a>>) -> Self {
124        self.right.push((text.into(), gap.into()));
125        self
126    }
127
128    /// Add to the end group of status flags.
129    /// These stack from right to left.
130    pub fn end_bare(mut self, text: impl Into<Line<'a>>) -> Self {
131        self.right.push((text.into(), "".into()));
132        self
133    }
134}
135
136impl<'a> Widget for StatusLineStacked<'a> {
137    fn render(self, area: Rect, buf: &mut Buffer) {
138        let mut x_end = area.right();
139        for (status, gap) in self.right {
140            let width = status.width() as u16;
141            status.style(self.style).render(
142                Rect::new(x_end.saturating_sub(width), area.y, width, 1),
143                buf,
144            );
145            x_end = x_end.saturating_sub(width);
146
147            let width = gap.width() as u16;
148            gap.style(self.style).render(
149                Rect::new(x_end.saturating_sub(width), area.y, width, 1),
150                buf,
151            );
152            x_end = x_end.saturating_sub(width);
153        }
154
155        let mut x_start = area.x;
156        for (status, gap) in self.left {
157            let width = status.width() as u16;
158            status
159                .style(self.style)
160                .render(Rect::new(x_start, area.y, width, 1), buf);
161            x_start += width;
162
163            let width = gap.width() as u16;
164            gap.style(self.style)
165                .render(Rect::new(x_start, area.y, width, 1), buf);
166            x_start += width;
167        }
168
169        // middle area
170        buf.set_style(
171            Rect::new(x_start, area.y, x_end.saturating_sub(x_start), 1),
172            self.style,
173        );
174
175        let center_width = x_end
176            .saturating_sub(x_start)
177            .saturating_sub(self.center_margin * 2);
178        self.center.style(self.style).render(
179            Rect::new(x_start + self.center_margin, area.y, center_width, 1),
180            buf,
181        );
182    }
183}