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