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}