rat_widget/
statusline.rs

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