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 {
98 Self::default()
99 }
100
101 pub fn layout<It, Item>(mut self, widths: It) -> Self
106 where
107 It: IntoIterator<Item = Item>,
108 Item: Into<Constraint>,
109 {
110 self.widths = widths.into_iter().map(|v| v.into()).collect();
111 self
112 }
113
114 pub fn styles(mut self, style: impl IntoIterator<Item = impl Into<Style>>) -> Self {
116 self.style = style.into_iter().map(|v| v.into()).collect();
117 self
118 }
119
120 pub fn styles_ext(mut self, styles: StatusLineStyle) -> Self {
122 self.sep = styles.sep;
123 self.style = styles.styles;
124 self
125 }
126}
127
128impl Default for StatusLineState {
129 fn default() -> Self {
130 Self {
131 area: Default::default(),
132 areas: Default::default(),
133 status: Default::default(),
134 non_exhaustive: NonExhaustive,
135 }
136 }
137}
138
139impl HasFocus for StatusLineState {
140 fn build(&self, _builder: &mut FocusBuilder) {
141 }
143
144 fn focus(&self) -> FocusFlag {
145 unimplemented!("not available")
146 }
147
148 fn area(&self) -> Rect {
149 unimplemented!("not available")
150 }
151}
152
153impl HasScreenCursor for StatusLineState {
154 fn screen_cursor(&self) -> Option<(u16, u16)> {
155 None
156 }
157}
158
159impl RelocatableState for StatusLineState {
160 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
161 self.area = relocate_area(self.area, shift, clip);
162 relocate_areas(self.areas.as_mut(), shift, clip);
163 }
164}
165
166impl StatusLineState {
167 pub fn new() -> Self {
168 Self::default()
169 }
170
171 pub fn named(_name: &str) -> Self {
173 Self::default()
174 }
175
176 pub fn clear_status(&mut self) {
178 self.status.clear();
179 }
180
181 pub fn status<S: Into<String>>(&mut self, idx: usize, msg: S) {
183 while self.status.len() <= idx {
184 self.status.push("".to_string());
185 }
186 self.status[idx] = msg.into();
187 }
188}
189
190impl StatefulWidget for &StatusLine {
191 type State = StatusLineState;
192
193 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
194 render_ref(self, area, buf, state);
195 }
196}
197
198impl StatefulWidget for StatusLine {
199 type State = StatusLineState;
200
201 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
202 render_ref(&self, area, buf, state);
203 }
204}
205
206fn render_ref(widget: &StatusLine, area: Rect, buf: &mut Buffer, state: &mut StatusLineState) {
207 state.area = area;
208
209 let layout = Layout::horizontal(widget.widths.iter()).split(state.area);
210
211 for (i, rect) in layout.iter().enumerate() {
212 let style = widget.style.get(i).copied().unwrap_or_default();
213 let txt = state.status.get(i).map(|v| v.as_str()).unwrap_or("");
214
215 let sep = if i > 0 {
216 if let Some(sep) = widget.sep.as_ref().map(|v| v.as_ref()) {
217 Span::from(sep)
218 } else {
219 Span::default()
220 }
221 } else {
222 Span::default()
223 };
224
225 Line::from_iter([
226 sep, Span::from(txt),
228 ])
229 .render(*rect, buf);
230
231 buf.set_style(*rect, style);
232 }
233}
234
235impl HandleEvent<crossterm::event::Event, Regular, Outcome> for StatusLineState {
236 fn handle(&mut self, _event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
237 Outcome::Continue
238 }
239}
240
241impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for StatusLineState {
242 fn handle(&mut self, _event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
243 Outcome::Continue
244 }
245}