duat_core/widgets/status_line/
state.rs

1//! The acceptable patterns in a [`StatusLine`]
2//!
3//! The patterns that can be put in a [`StatusLine`] are largely the
4//! same as the ones that can be put inside a [`text!`] macro,
5//! however, some additions are made.
6//!
7//! Specifically, arguments that read from the [`File`] and its
8//! related structs are also accepted by [`status!`].
9//!
10//! In addition, arguments with arbitrary update schedules are also
11//! accepted, such as the [data] types, and parser/checker function
12//! pairs.
13//!
14//! [`StatusLine`]: super::StatusLine
15//! [`status!`]: super::status
16//! [data]: crate::data
17use std::{fmt::Display, marker::PhantomData};
18
19use crate::{
20    context::FileReader,
21    data::{DataMap, RoData, RwData},
22    mode::Cursors,
23    text::{Builder, Tag, Text, text},
24    ui::Ui,
25    widgets::{File, Widget},
26};
27
28/// A struct that reads state in order to return [`Text`].
29enum Appender<T> {
30    NoArgs(Box<dyn FnMut() -> Append + Send + Sync + 'static>),
31    FromWidget(RelatedFn<T>),
32    FromFileAndWidget(FileAndRelatedFn<T>),
33    FromCursors(RelatedFn<Cursors>),
34    Str(String),
35    Text(Text),
36}
37
38/// A part of the [`StatusLine`]
39///
40/// This can either be a static part, like [`Text`], [`impl Display`]
41/// type, or it can be a reader of the [`File`] and its structs, or it
42/// can update independently.
43///
44/// [`StatusLine`]: super::StatusLine
45/// [`impl Display`]: std::fmt::Display
46pub struct State<T: 'static, Dummy, U> {
47    appender: Appender<T>,
48    checker: Option<Box<dyn Fn() -> bool + Send + Sync>>,
49    ghost: PhantomData<(Dummy, U)>,
50}
51
52impl<T: 'static, Dummy, U: Ui> State<T, Dummy, U> {
53    pub fn fns(self) -> (ReaderFn<U>, Box<dyn Fn() -> bool + Send + Sync>) {
54        (
55            match self.appender {
56                Appender::NoArgs(mut f) => Box::new(move |builder, _| f().push_to(builder)),
57                Appender::FromWidget(mut f) => Box::new(move |builder, reader| {
58                    if let Some(append) = reader.inspect_related(&mut f) {
59                        append.push_to(builder)
60                    }
61                }),
62                Appender::FromFileAndWidget(mut f) => Box::new(move |builder, reader| {
63                    if let Some(append) = reader.inspect_file_and(|file, widget| f(file, widget)) {
64                        append.push_to(builder)
65                    }
66                }),
67                Appender::FromCursors(mut f) => Box::new(move |builder, reader| {
68                    reader
69                        .inspect(|file, _| f(file.cursors().unwrap()))
70                        .push_to(builder);
71                }),
72                Appender::Str(str) => Box::new(move |builder, _| {
73                    if !(str == " " && builder.last_was_empty()) {
74                        builder.push_str(&str)
75                    }
76                }),
77                Appender::Text(text) => Box::new(move |builder, _| builder.push_text(text.clone())),
78            },
79            Box::new(move || self.checker.as_ref().is_some_and(|check| check())),
80        )
81    }
82}
83
84impl<D: Display + Send + Sync, U: Ui> From<D> for State<(), String, U> {
85    fn from(value: D) -> Self {
86        Self {
87            appender: Appender::Str::<()>(value.to_string()),
88            checker: None,
89            ghost: PhantomData,
90        }
91    }
92}
93
94impl<U: Ui> From<Text> for State<(), Text, U> {
95    fn from(value: Text) -> Self {
96        Self {
97            appender: Appender::Text::<()>(value),
98            checker: None,
99            ghost: PhantomData,
100        }
101    }
102}
103
104impl<U: Ui> From<Tag> for State<(), Tag, U> {
105    fn from(value: Tag) -> Self {
106        Self {
107            appender: Appender::Text::<()>(text!(value)),
108            checker: None,
109            ghost: PhantomData,
110        }
111    }
112}
113
114impl<D: Display + Send + Sync, U: Ui> From<RwData<D>> for State<(), DataArg<String>, U> {
115    fn from(value: RwData<D>) -> Self {
116        Self {
117            appender: Appender::NoArgs::<()>({
118                let value = RoData::from(&value);
119                Box::new(move || Append::String(value.read().to_string()))
120            }),
121            checker: Some(Box::new(move || value.has_changed())),
122            ghost: PhantomData,
123        }
124    }
125}
126
127impl<U: Ui> From<RwData<Text>> for State<(), DataArg<Text>, U> {
128    fn from(value: RwData<Text>) -> Self {
129        Self {
130            appender: Appender::NoArgs::<()>({
131                let value = RoData::from(&value);
132                Box::new(move || Append::Text(value.read().clone()))
133            }),
134            checker: Some(Box::new(move || value.has_changed())),
135            ghost: PhantomData,
136        }
137    }
138}
139
140impl<D: Display + Send + Sync, U: Ui> From<RoData<D>> for State<(), DataArg<String>, U> {
141    fn from(value: RoData<D>) -> Self {
142        Self {
143            appender: Appender::NoArgs::<()>({
144                let value = value.clone();
145                Box::new(move || Append::String(value.read().to_string()))
146            }),
147            checker: Some(Box::new(move || value.has_changed())),
148            ghost: PhantomData,
149        }
150    }
151}
152
153impl<U: Ui> From<RoData<Text>> for State<(), DataArg<Text>, U> {
154    fn from(value: RoData<Text>) -> Self {
155        Self {
156            appender: Appender::NoArgs::<()>({
157                let value = value.clone();
158                Box::new(move || Append::Text(value.read().clone()))
159            }),
160            checker: Some(Box::new(move || value.has_changed())),
161            ghost: PhantomData,
162        }
163    }
164}
165
166impl<U, I, O> From<DataMap<I, O>> for State<(), DataArg<String>, U>
167where
168    U: Ui,
169    I: ?Sized + Send + Sync,
170    O: Display + 'static,
171{
172    fn from(value: DataMap<I, O>) -> Self {
173        let (mut reader, checker) = value.fns();
174        State {
175            appender: Appender::NoArgs(Box::new(move || Append::String(reader().to_string()))),
176            checker: Some(checker),
177            ghost: PhantomData,
178        }
179    }
180}
181
182impl<U, I> From<DataMap<I, Text>> for State<(), DataArg<Text>, U>
183where
184    U: Ui,
185    I: ?Sized + Send + Sync,
186{
187    fn from(value: DataMap<I, Text>) -> Self {
188        let (mut reader, checker) = value.fns();
189        let reader = move || Append::Text(reader());
190        State {
191            appender: Appender::NoArgs(Box::new(reader)),
192            checker: Some(checker),
193            ghost: PhantomData,
194        }
195    }
196}
197
198impl<U, F, I, O> From<F> for State<(), IntoDataArg<String>, U>
199where
200    U: Ui,
201    F: FnOnce() -> DataMap<I, O>,
202    I: ?Sized + Send + Sync + 'static,
203    O: Display + 'static,
204{
205    fn from(value: F) -> Self {
206        let (mut reader, checker) = value().fns();
207        State {
208            appender: Appender::NoArgs(Box::new(move || Append::String(reader().to_string()))),
209            checker: Some(checker),
210            ghost: PhantomData,
211        }
212    }
213}
214
215impl<U, F, I> From<F> for State<(), IntoDataArg<Text>, U>
216where
217    U: Ui,
218    F: FnOnce() -> DataMap<I, Text>,
219    I: ?Sized + Send + Sync + 'static,
220{
221    fn from(value: F) -> Self {
222        let (mut reader, checker) = value().fns();
223        let reader = move || Append::Text(reader());
224        State {
225            appender: Appender::NoArgs(Box::new(reader)),
226            checker: Some(checker),
227            ghost: PhantomData,
228        }
229    }
230}
231
232impl<D, Reader, Checker, U> From<(Reader, Checker)> for State<(), NoArg<String>, U>
233where
234    D: Display,
235    Reader: Fn() -> D + Send + Sync + 'static,
236    Checker: Fn() -> bool + Send + Sync + 'static,
237    U: Ui,
238{
239    fn from((reader, checker): (Reader, Checker)) -> Self {
240        let reader = move || Append::String(reader().to_string());
241        State {
242            appender: Appender::NoArgs::<()>(Box::new(reader)),
243            checker: Some(Box::new(checker)),
244            ghost: PhantomData,
245        }
246    }
247}
248
249impl<Reader, Checker, U> From<(Reader, Checker)> for State<(), NoArg<Text>, U>
250where
251    Reader: Fn() -> Text + Send + Sync + 'static,
252    Checker: Fn() -> bool + Send + Sync + 'static,
253    U: Ui,
254{
255    fn from((reader, checker): (Reader, Checker)) -> Self {
256        let reader = move || Append::Text(reader());
257        State {
258            appender: Appender::NoArgs::<()>(Box::new(reader)),
259            checker: Some(Box::new(checker)),
260            ghost: PhantomData,
261        }
262    }
263}
264
265impl<D, W, ReadFn, U> From<ReadFn> for State<W, WidgetArg<String>, U>
266where
267    D: Display + Send + Sync,
268    W: Widget<U> + Sized,
269    ReadFn: Fn(&W) -> D + Send + Sync + 'static,
270    U: Ui,
271{
272    fn from(reader: ReadFn) -> Self {
273        let reader = move |arg: &W| Append::String(reader(arg).to_string());
274        State {
275            appender: Appender::FromWidget(Box::new(reader)),
276            checker: None,
277            ghost: PhantomData,
278        }
279    }
280}
281
282impl<W, ReadFn, U> From<ReadFn> for State<W, WidgetArg<Text>, U>
283where
284    W: Widget<U> + Sized,
285    ReadFn: Fn(&W) -> Text + Send + Sync + 'static,
286    U: Ui,
287{
288    fn from(reader: ReadFn) -> Self {
289        let reader = move |arg: &W| Append::Text(reader(arg));
290        State {
291            appender: Appender::FromWidget(Box::new(reader)),
292            checker: None,
293            ghost: PhantomData,
294        }
295    }
296}
297
298impl<D, W, ReadFn, U> From<ReadFn> for State<W, FileAndWidgetArg<String>, U>
299where
300    D: Display + Send + Sync,
301    W: Widget<U>,
302    ReadFn: Fn(&File, &W) -> D + Send + Sync + 'static,
303    U: Ui,
304{
305    fn from(reader: ReadFn) -> Self {
306        let reader = move |file: &File, arg: &W| Append::String(reader(file, arg).to_string());
307        State {
308            appender: Appender::FromFileAndWidget(Box::new(reader)),
309            checker: None,
310            ghost: PhantomData,
311        }
312    }
313}
314
315impl<W, ReadFn, U> From<ReadFn> for State<W, FileAndWidgetArg<Text>, U>
316where
317    W: Widget<U>,
318    ReadFn: Fn(&File, &W) -> Text + Send + Sync + 'static,
319    U: Ui,
320{
321    fn from(reader: ReadFn) -> Self {
322        let reader = move |file: &File, w: &W| Append::Text(reader(file, w));
323        State {
324            appender: Appender::FromFileAndWidget(Box::new(reader)),
325            checker: None,
326            ghost: PhantomData,
327        }
328    }
329}
330
331impl<D, ReadFn, U> From<ReadFn> for State<Cursors, CursorsArg<String>, U>
332where
333    D: Display + Send + Sync,
334    ReadFn: Fn(&Cursors) -> D + Send + Sync + 'static,
335    U: Ui,
336{
337    fn from(reader: ReadFn) -> Self {
338        let reader = move |arg: &Cursors| Append::String(reader(arg).to_string());
339        State {
340            appender: Appender::FromCursors(Box::new(reader)),
341            checker: None,
342            ghost: PhantomData,
343        }
344    }
345}
346
347impl<ReadFn, U> From<ReadFn> for State<Cursors, CursorsArg<Text>, U>
348where
349    ReadFn: Fn(&Cursors) -> Text + Send + Sync + 'static,
350    U: Ui,
351{
352    fn from(reader: ReadFn) -> Self {
353        let reader = move |arg: &Cursors| Append::Text(reader(arg));
354        State {
355            appender: Appender::FromCursors(Box::new(reader)),
356            checker: None,
357            ghost: PhantomData,
358        }
359    }
360}
361
362// Dummy structs to prevent implementation conflicts.
363#[doc(hidden)]
364pub struct DataArg<T>(PhantomData<T>);
365#[doc(hidden)]
366pub struct IntoDataArg<T>(PhantomData<T>);
367#[doc(hidden)]
368pub struct NoArg<T>(PhantomData<T>);
369#[doc(hidden)]
370pub struct WidgetArg<T>(PhantomData<T>);
371#[doc(hidden)]
372pub struct FileAndWidgetArg<T>(PhantomData<T>);
373#[doc(hidden)]
374pub struct CursorsArg<T>(PhantomData<T>);
375
376// The various types of function aliases
377type RelatedFn<T> = Box<dyn FnMut(&T) -> Append + Send + Sync + 'static>;
378type FileAndRelatedFn<T> = Box<dyn FnMut(&File, &T) -> Append + Send + Sync + 'static>;
379
380type ReaderFn<U> = Box<dyn FnMut(&mut Builder, &FileReader<U>) + Send + Sync>;
381
382enum Append {
383    String(String),
384    Text(Text),
385}
386
387impl Append {
388    fn push_to(self, builder: &mut Builder) {
389        match self {
390            Append::String(string) => builder.push_str(&string),
391            Append::Text(text) => builder.push_text(text),
392        }
393    }
394}