1use 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::{Line, Span};
43use ratatui::widgets::{StatefulWidget, Widget};
44use std::borrow::Cow;
45use std::fmt::Debug;
46
47#[derive(Debug, Default, Clone)]
49pub struct StatusLine {
50 sep: Option<Cow<'static, str>>,
51 style: Vec<Style>,
52 widths: Vec<Constraint>,
53}
54
55#[derive(Debug, Clone)]
57pub struct StatusLineStyle {
58 pub sep: Option<Cow<'static, str>>,
60 pub styles: Vec<Style>,
62 pub non_exhaustive: NonExhaustive,
63}
64
65#[derive(Debug, Clone)]
67pub struct StatusLineState {
68 pub area: Rect,
71 pub areas: Vec<Rect>,
74
75 pub status: Vec<String>,
78
79 pub non_exhaustive: NonExhaustive,
80}
81
82impl Default for StatusLineStyle {
83 fn default() -> Self {
84 Self {
85 sep: Default::default(),
86 styles: Default::default(),
87 non_exhaustive: NonExhaustive,
88 }
89 }
90}
91
92impl StatusLine {
93 pub fn new() -> Self {
95 Self {
96 sep: Default::default(),
97 style: Default::default(),
98 widths: Default::default(),
99 }
100 }
101
102 pub fn layout<It, Item>(mut self, widths: It) -> Self
107 where
108 It: IntoIterator<Item = Item>,
109 Item: Into<Constraint>,
110 {
111 self.widths = widths.into_iter().map(|v| v.into()).collect();
112 self
113 }
114
115 pub fn styles(mut self, style: impl IntoIterator<Item = impl Into<Style>>) -> Self {
117 self.style = style.into_iter().map(|v| v.into()).collect();
118 self
119 }
120
121 pub fn styles_ext(mut self, styles: StatusLineStyle) -> Self {
123 self.sep = styles.sep;
124 self.style = styles.styles;
125 self
126 }
127}
128
129impl Default for StatusLineState {
130 fn default() -> Self {
131 Self {
132 area: Default::default(),
133 areas: Default::default(),
134 status: Default::default(),
135 non_exhaustive: NonExhaustive,
136 }
137 }
138}
139
140impl RelocatableState for StatusLineState {
141 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
142 self.area = relocate_area(self.area, shift, clip);
143 relocate_areas(self.areas.as_mut(), shift, clip);
144 }
145}
146
147impl StatusLineState {
148 pub fn new() -> Self {
149 Self::default()
150 }
151
152 pub fn clear_status(&mut self) {
154 self.status.clear();
155 }
156
157 pub fn status<S: Into<String>>(&mut self, idx: usize, msg: S) {
159 while self.status.len() <= idx {
160 self.status.push("".to_string());
161 }
162 self.status[idx] = msg.into();
163 }
164}
165
166impl StatefulWidget for &StatusLine {
167 type State = StatusLineState;
168
169 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
170 render_ref(self, area, buf, state);
171 }
172}
173
174impl StatefulWidget for StatusLine {
175 type State = StatusLineState;
176
177 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
178 render_ref(&self, area, buf, state);
179 }
180}
181
182fn render_ref(widget: &StatusLine, area: Rect, buf: &mut Buffer, state: &mut StatusLineState) {
183 state.area = area;
184
185 let layout = Layout::horizontal(widget.widths.iter()).split(state.area);
186
187 for (i, rect) in layout.iter().enumerate() {
188 let style = widget.style.get(i).copied().unwrap_or_default();
189 let txt = state.status.get(i).map(|v| v.as_str()).unwrap_or("");
190
191 let sep = if i > 0 {
192 if let Some(sep) = widget.sep.as_ref().map(|v| v.as_ref()) {
193 Span::from(sep)
194 } else {
195 Span::default()
196 }
197 } else {
198 Span::default()
199 };
200
201 Line::from_iter([
202 sep, Span::from(txt),
204 ])
205 .render(*rect, buf);
206
207 buf.set_style(*rect, style);
208 }
209}