1use crate::_private::NonExhaustive;
38use crate::text::HasScreenCursor;
39use rat_event::{HandleEvent, MouseOnly, Outcome, Regular};
40use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
41use rat_reloc::{RelocatableState, relocate_area, relocate_areas};
42use ratatui::buffer::Buffer;
43use ratatui::layout::{Constraint, Layout, Rect};
44use ratatui::style::Style;
45use ratatui::text::{Line, Span};
46use ratatui::widgets::{StatefulWidget, Widget};
47use std::borrow::Cow;
48use std::fmt::Debug;
49
50#[derive(Debug, Default, Clone)]
52pub struct StatusLine {
53 sep: Option<Cow<'static, str>>,
54 style: Vec<Style>,
55 widths: Vec<Constraint>,
56}
57
58#[derive(Debug, Clone)]
60pub struct StatusLineStyle {
61 pub sep: Option<Cow<'static, str>>,
63 pub styles: Vec<Style>,
65 pub non_exhaustive: NonExhaustive,
66}
67
68#[derive(Debug, Clone)]
70pub struct StatusLineState {
71 pub area: Rect,
74 pub areas: Vec<Rect>,
77
78 pub status: Vec<String>,
81
82 pub non_exhaustive: NonExhaustive,
83}
84
85impl Default for StatusLineStyle {
86 fn default() -> Self {
87 Self {
88 sep: Default::default(),
89 styles: Default::default(),
90 non_exhaustive: NonExhaustive,
91 }
92 }
93}
94
95impl StatusLine {
96 pub fn new() -> Self {
100 Self::default()
101 }
102
103 pub fn layout<It, Item>(mut self, widths: It) -> Self
108 where
109 It: IntoIterator<Item = Item>,
110 Item: Into<Constraint>,
111 {
112 self.widths = widths.into_iter().map(|v| v.into()).collect();
113 self
114 }
115
116 pub fn styles(mut self, style: impl IntoIterator<Item = impl Into<Style>>) -> Self {
118 self.style = style.into_iter().map(|v| v.into()).collect();
119 self
120 }
121
122 pub fn styles_ext(mut self, styles: StatusLineStyle) -> Self {
124 self.sep = styles.sep;
125 self.style = styles.styles;
126 self
127 }
128}
129
130impl Default for StatusLineState {
131 fn default() -> Self {
132 Self {
133 area: Default::default(),
134 areas: Default::default(),
135 status: Default::default(),
136 non_exhaustive: NonExhaustive,
137 }
138 }
139}
140
141impl HasFocus for StatusLineState {
142 fn build(&self, _builder: &mut FocusBuilder) {
143 }
145
146 fn focus(&self) -> FocusFlag {
147 unimplemented!("not available")
148 }
149
150 fn area(&self) -> Rect {
151 unimplemented!("not available")
152 }
153}
154
155impl HasScreenCursor for StatusLineState {
156 fn screen_cursor(&self) -> Option<(u16, u16)> {
157 None
158 }
159}
160
161impl RelocatableState for StatusLineState {
162 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
163 self.area = relocate_area(self.area, shift, clip);
164 relocate_areas(self.areas.as_mut(), shift, clip);
165 }
166}
167
168impl StatusLineState {
169 pub fn new() -> Self {
170 Self::default()
171 }
172
173 pub fn named(_name: &str) -> Self {
175 Self::default()
176 }
177
178 pub fn clear_status(&mut self) {
180 self.status.clear();
181 }
182
183 pub fn status<S: Into<String>>(&mut self, idx: usize, msg: S) {
185 while self.status.len() <= idx {
186 self.status.push("".to_string());
187 }
188 self.status[idx] = msg.into();
189 }
190}
191
192impl StatefulWidget for &StatusLine {
193 type State = StatusLineState;
194
195 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
196 render_ref(self, area, buf, state);
197 }
198}
199
200impl StatefulWidget for StatusLine {
201 type State = StatusLineState;
202
203 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
204 render_ref(&self, area, buf, state);
205 }
206}
207
208fn render_ref(widget: &StatusLine, area: Rect, buf: &mut Buffer, state: &mut StatusLineState) {
209 state.area = area;
210
211 let layout = Layout::horizontal(widget.widths.iter()).split(state.area);
212
213 for (i, rect) in layout.iter().enumerate() {
214 let style = widget.style.get(i).copied().unwrap_or_default();
215 let txt = state.status.get(i).map(|v| v.as_str()).unwrap_or("");
216
217 let sep = if i > 0 {
218 if let Some(sep) = widget.sep.as_ref().map(|v| v.as_ref()) {
219 Span::from(sep)
220 } else {
221 Span::default()
222 }
223 } else {
224 Span::default()
225 };
226
227 Line::from_iter([
228 sep, Span::from(txt),
230 ])
231 .render(*rect, buf);
232
233 buf.set_style(*rect, style);
234 }
235}
236
237impl HandleEvent<crossterm::event::Event, Regular, Outcome> for StatusLineState {
238 fn handle(&mut self, _event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
239 Outcome::Continue
240 }
241}
242
243impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for StatusLineState {
244 fn handle(&mut self, _event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
245 Outcome::Continue
246 }
247}