rat_widget/
statusline.rs

1//!
2//! Statusbar with multiple sections.
3//!
4//! ```
5//!
6//! use ratatui::buffer::Buffer;
7//! use ratatui::layout::{Constraint, Rect};
8//! use ratatui::style::{Style, Stylize};
9//! use ratatui::widgets::StatefulWidget;
10//! use rat_widget::statusline::{StatusLine, StatusLineState};
11//!
12//! let mut status_line_state = StatusLineState::new();
13//! status_line_state.status(0, "Everything's fine.");
14//! status_line_state.status(1, "50%");
15//! status_line_state.status(2, "72%");
16//!
17//!
18//! # let area = Rect::new(0,24,80,1);
19//! # let mut buf = Buffer::empty(area);
20//! # let buf = &mut buf;
21//!
22//! StatusLine::new()
23//!     .layout([
24//!         Constraint::Fill(1),
25//!         Constraint::Length(8),
26//!         Constraint::Length(8)
27//!     ])
28//!     .styles([
29//!         Style::new().white().on_dark_gray(),
30//!         Style::new().white().on_cyan(),
31//!         Style::new().white().on_blue()
32//!     ])
33//!     .render(area, buf, &mut status_line_state);
34//!
35//! ```
36
37use crate::_private::NonExhaustive;
38use rat_reloc::{RelocatableState, relocate_area, relocate_areas};
39use ratatui::buffer::Buffer;
40use ratatui::layout::{Constraint, Layout, Rect};
41use ratatui::style::Style;
42use ratatui::text::Span;
43use ratatui::widgets::{StatefulWidget, Widget};
44use std::fmt::Debug;
45
46/// Statusbar with multiple sections.
47#[derive(Debug, Default, Clone)]
48pub struct StatusLine {
49    style: Vec<Style>,
50    widths: Vec<Constraint>,
51}
52
53/// State & event handling.
54#[derive(Debug, Clone)]
55pub struct StatusLineState {
56    /// Total area
57    /// __readonly__. renewed for each render.
58    pub area: Rect,
59    /// Areas for each section.
60    /// __readonly__. renewed for each render.
61    pub areas: Vec<Rect>,
62
63    /// Statustext for each section.
64    /// __read+write__
65    pub status: Vec<String>,
66
67    pub non_exhaustive: NonExhaustive,
68}
69
70impl StatusLine {
71    /// New widget.
72    pub fn new() -> Self {
73        Self {
74            style: Default::default(),
75            widths: Default::default(),
76        }
77    }
78
79    /// Layout for the sections.
80    ///
81    /// This layout determines the number of sections.
82    /// If the styles or the status text vec differ, defaults are used.
83    pub fn layout<It, Item>(mut self, widths: It) -> Self
84    where
85        It: IntoIterator<Item = Item>,
86        Item: Into<Constraint>,
87    {
88        self.widths = widths.into_iter().map(|v| v.into()).collect();
89        self
90    }
91
92    /// Styles for each section.
93    pub fn styles(mut self, style: impl IntoIterator<Item = impl Into<Style>>) -> Self {
94        self.style = style.into_iter().map(|v| v.into()).collect();
95        self
96    }
97}
98
99impl Default for StatusLineState {
100    fn default() -> Self {
101        Self {
102            area: Default::default(),
103            areas: Default::default(),
104            status: Default::default(),
105            non_exhaustive: NonExhaustive,
106        }
107    }
108}
109
110impl RelocatableState for StatusLineState {
111    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
112        self.area = relocate_area(self.area, shift, clip);
113        relocate_areas(self.areas.as_mut(), shift, clip);
114    }
115}
116
117impl StatusLineState {
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    /// Clear all status text.
123    pub fn clear_status(&mut self) {
124        self.status.clear();
125    }
126
127    /// Set the specific status section.
128    pub fn status<S: Into<String>>(&mut self, idx: usize, msg: S) {
129        while self.status.len() <= idx {
130            self.status.push("".to_string());
131        }
132        self.status[idx] = msg.into();
133    }
134}
135
136impl StatefulWidget for &StatusLine {
137    type State = StatusLineState;
138
139    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
140        render_ref(self, area, buf, state);
141    }
142}
143
144impl StatefulWidget for StatusLine {
145    type State = StatusLineState;
146
147    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
148        render_ref(&self, area, buf, state);
149    }
150}
151
152fn render_ref(widget: &StatusLine, area: Rect, buf: &mut Buffer, state: &mut StatusLineState) {
153    state.area = area;
154
155    let layout = Layout::horizontal(widget.widths.iter()).split(state.area);
156
157    for (i, rect) in layout.iter().enumerate() {
158        let style = widget.style.get(i).copied().unwrap_or_default();
159        let txt = state.status.get(i).map(|v| v.as_str()).unwrap_or("");
160
161        buf.set_style(*rect, style);
162        Span::from(txt).render(*rect, buf);
163    }
164}