duat_core/widgets/
mod.rs

1//! APIs for the construction of widgets, and a few common ones.
2//!
3//! This module has the [`Widget`] trait, which is a region on the
4//! window containing a [`Text`], and may be modified by user mode
5//! (but not necessarily).
6//!
7//! With the exception of the [`File`], these widgets will show up in
8//! one of three contexts:
9//!
10//! - Being pushed to a [`File`] via the hook [`OnFileOpen`];
11//! - Being pushed to the outer edges via [`OnWindowOpen`];
12//! - Being pushed to popup widgets via `OnPopupOpen` (TODO);
13//!
14//! These widgets can be pushed to all 4 sides of other widgets,
15//! through the use of [`PushSpecs`]. When pushing widgets, you can
16//! also include [`Constraint`] in order to get a specific size on the
17//! screen for the widget.
18//!
19//! ```rust
20//! # use duat_core::ui::PushSpecs;
21//! let specs = PushSpecs::left().with_hor_min(10.0).with_ver_len(2.0);
22//! ```
23//!
24//! When pushing a widget with these `specs` to another widget, Duat
25//! will put it on the left, and _try_ to give it a minimum width of
26//! `10.0`, and a height of `2.0`.
27//!
28//! The module also provides 4 native widgets, [`File`] and
29//! [`CmdLine`], which can receive user mode, and [`StatusLine`]
30//! and [`LineNumbers`] which are not supposed to.
31//!
32//! These 4 widgets are supposed to be universal, not needing a
33//! specific [`Ui`] implementation to work. In contrast, you can
34//! create widgets for specific [`Ui`]s. As an example, the
35//! [`duat-term`] crate, which is a terminal [`Ui`] implementation for
36//! Duat, defines the [`VertRule`] widget, which is a separator that
37//! only makes sense in the context of a terminal.
38//!
39//! This module also describes a [`WidgetCfg`], which is used in
40//! widget construction.
41//!
42//! [`duat-term`]: https://docs.rs/duat-term/latest/duat_term/
43//! [`VertRule`]: https://docs.rs/duat-term/latest/duat_term/struct.VertRule.html
44//! [`OnFileOpen`]: crate::hooks::OnFileOpen
45//! [`OnWindowOpen`]: crate::hooks::OnWindowOpen
46//! [`Constraint`]: crate::ui::Constraint
47use std::sync::{
48    Arc,
49    atomic::{AtomicBool, Ordering},
50};
51
52pub use self::{
53    command_line::{
54        CmdLine, CmdLineCfg, CmdLineMode, IncSearch, PipeSelections, RunCommands, ShowNotifications,
55    },
56    file::{File, FileCfg, PathKind},
57    line_numbers::{LineNum, LineNumbers, LineNumbersCfg},
58    status_line::{State, StatusLine, StatusLineCfg, common, status},
59};
60use crate::{
61    cfg::PrintCfg,
62    context::FileParts,
63    data::{Data, RwData},
64    form,
65    hooks::{self, FocusedOn, UnfocusedFrom},
66    mode::Cursors,
67    text::Text,
68    ui::{Area, PushSpecs, Ui},
69};
70
71mod command_line;
72mod file;
73mod line_numbers;
74mod status_line;
75
76/// An area where [`Text`] will be printed to the screen
77///
78/// Most widgets are supposed to be passive widgets, that simply show
79/// information about the current state of Duat. If you want to see
80/// how to create a widget that takes in mode, see [`Mode`]
81///
82/// In order to show that information, widgets make use of [`Text`],
83/// which can show stylized text, buttons, and all sorts of other
84/// stuff.
85///
86/// For a demonstration on how to create a widget, I will create a
87/// widget that shows the uptime, in seconds, for Duat.
88///
89/// ```rust
90/// # use duat_core::text::Text;
91/// struct UpTime(Text);
92/// ```
93///
94/// In order to be a proper widget, it must have a [`Text`] to
95/// display. Next, I must implement [`Widget`]:
96///
97/// ```rust
98/// # use std::{marker::PhantomData, sync::OnceLock, time::{Duration, Instant}};
99/// # use duat_core::{
100/// #     hooks, periodic_checker, text::Text, ui::{PushSpecs, Ui},
101/// #     widgets::{Widget, WidgetCfg},
102/// # };
103/// # struct UpTime(Text);
104/// # struct UpTimeCfg<U>(PhantomData<U>);
105/// # impl<U: Ui> WidgetCfg<U> for UpTimeCfg<U> {
106/// #     type Widget = UpTime;
107/// #     fn build(self,_: bool) -> (Self::Widget, impl Fn() -> bool + 'static, PushSpecs) {
108/// #         (UpTime(Text::new()), || false, PushSpecs::below())
109/// #     }
110/// # }
111/// impl<U: Ui> Widget<U> for UpTime {
112///     type Cfg = UpTimeCfg<U>;
113///
114///     fn cfg() -> Self::Cfg {
115///         UpTimeCfg(PhantomData)
116///     }
117///     // ...
118/// #     fn text(&self) -> &Text {
119/// #         &self.0
120/// #     }
121/// #     fn text_mut(&mut self) -> &mut Text {
122/// #         &mut self.0
123/// #     }
124/// #     fn once() -> Result<(), duat_core::Error<()>> {
125/// #         Ok(())
126/// #     }
127/// }
128/// ```
129///
130/// Note the [`Cfg`](Widget::Cfg) type, and the [`cfg`] method.
131/// These exist to give the user the ability to modify the widgets
132/// before they are pushed. The `Cfg` type, which implements
133/// [`WidgetCfg`] is the thing that will actually construct the
134/// widget. Let's look at `UpTimeCfg`:
135///
136/// ```rust
137/// # use std::{marker::PhantomData, sync::OnceLock, time::{Duration, Instant}};
138/// # use duat_core::{
139/// #     hooks, periodic_checker, text::Text, ui::{PushSpecs, Ui}, widgets::{Widget, WidgetCfg},
140/// # };
141/// # struct UpTime(Text);
142/// struct UpTimeCfg<U>(PhantomData<U>);
143///
144/// impl<U: Ui> WidgetCfg<U> for UpTimeCfg<U> {
145///     type Widget = UpTime;
146///
147///     fn build(self, on_file: bool) -> (UpTime, impl Fn() -> bool + 'static, PushSpecs) {
148///         let widget = UpTime(Text::new());
149///         let checker = periodic_checker(Duration::new(1, 0));
150///         let specs = PushSpecs::below().with_ver_len(1.0);
151///
152///         (widget, checker, specs)
153///     }
154/// }
155/// # impl<U: Ui> Widget<U> for UpTime {
156/// #     type Cfg = UpTimeCfg<U>;
157/// #     fn cfg() -> Self::Cfg {
158/// #         UpTimeCfg(PhantomData)
159/// #     }
160/// #     fn text(&self) -> &Text {
161/// #         &self.0
162/// #     }
163/// #     fn text_mut(&mut self) -> &mut Text{
164/// #         &mut self.0
165/// #     }
166/// #     fn once() -> Result<(), duat_core::Error<()>> {
167/// #         Ok(())
168/// #     }
169/// # }
170/// ```
171///
172/// The [`build`] method should return 3 objects:
173///
174/// * The widget itself.
175/// * A checker function that tells Duat when to update the widget.
176/// * [How] to push the widget into the [`File`]/window.
177///
178/// In this case, [`periodic_checker`] returns a function that returns
179/// `true` every `duration` that passes.
180///
181/// Also, note that `UpTimeCfg` includes a [`PhantomData<U>`]. This is
182/// done so that the end user does not need to specify a [`Ui`] when
183/// using [`WidgetCfg`]s.
184///
185/// Now, there are some other methods from [`Widget`] that need
186/// to be implemented for this to work. First of all, there needs to
187/// be a starting [`Instant`] to compare with the current moment in
188/// time.
189///
190/// The best time to do something like this is after Duat is done with
191/// initial setup. This happens when the [`ConfigLoaded`] hook is
192/// triggered.
193///
194/// ```rust
195/// # use std::{sync::OnceLock, time::Instant};
196/// # use duat_core::{hooks::{self, ConfigLoaded}, ui::Ui};
197/// # fn test<U: Ui>() {
198/// static START_TIME: OnceLock<Instant> = OnceLock::new();
199/// hooks::add::<ConfigLoaded>(|_| START_TIME.set(Instant::now()).unwrap());
200/// # }
201/// ```
202///
203/// I could put this code inside the [`cfg`] method, however, by
204/// doing so, it will be called every time this widget is added to the
205/// ui.
206///
207/// Instead, I'll put it in [`Widget::once`]. This function is
208/// only triggered once, no matter how many times the widget is added
209/// to the ui:
210///
211/// ```rust
212/// # use std::{marker::PhantomData, sync::OnceLock, time::{Duration, Instant}};
213/// # use duat_core::{
214/// #     form::{self, Form}, hooks::{self, ConfigLoaded}, periodic_checker,
215/// #     text::Text, ui::{PushSpecs, Ui}, widgets::{Widget, WidgetCfg},
216/// # };
217/// # struct UpTime(Text);
218/// # struct UpTimeCfg<U>(PhantomData<U>);
219/// # impl<U: Ui> WidgetCfg<U> for UpTimeCfg<U> {
220/// #     type Widget = UpTime;
221/// #     fn build(self, on_file: bool) -> (UpTime, impl Fn() -> bool + 'static, PushSpecs) {
222/// #         (UpTime(Text::new()), || false, PushSpecs::below())
223/// #     }
224/// # }
225/// static START_TIME: OnceLock<Instant> = OnceLock::new();
226///
227/// impl<U: Ui> Widget<U> for UpTime {
228/// #     type Cfg = UpTimeCfg<U>;
229/// #     fn cfg() -> Self::Cfg {
230/// #         UpTimeCfg(PhantomData)
231/// #     }
232/// #     fn text(&self) -> &Text {
233/// #         &self.0
234/// #     }
235/// #     fn text_mut(&mut self) -> &mut Text {
236/// #         &mut self.0
237/// #     }
238///     // ...
239///     fn once() -> Result<(), duat_core::Error<()>> {
240///         hooks::add::<ConfigLoaded>(|_| {
241///             START_TIME.set(Instant::now()).unwrap()
242///         });
243///         form::set_weak("UpTime", Form::cyan());
244///         Ok(())
245///     }
246/// }
247/// ```
248///
249/// I also added the `"UpTime"` [`Form`], which will be used by the
250/// widget when it is updated. When adding form, you should use the
251/// [`form::set_weak*`] functions, in order to not interfere with
252/// the configuration crate.
253///
254/// Next, I need to implement the [`update`] method, which will simply
255/// format the [`Text`] into a readable format:
256///
257/// ```rust
258/// # use std::{marker::PhantomData, sync::OnceLock, time::{Duration, Instant}};
259/// # use duat_core::{
260/// #     hooks, periodic_checker, text::{Text, text}, ui::{PushSpecs, Ui},
261/// #     widgets::{Widget, WidgetCfg},
262/// # };
263/// # struct UpTime(Text);
264/// # struct UpTimeCfg<U>(PhantomData<U>);
265/// # impl<U: Ui> WidgetCfg<U> for UpTimeCfg<U> {
266/// #     type Widget = UpTime;
267/// #     fn build(self, on_file: bool) -> (UpTime, impl Fn() -> bool + 'static, PushSpecs) {
268/// #         (UpTime(Text::new()), || false, PushSpecs::below())
269/// #     }
270/// # }
271/// # static START_TIME: OnceLock<Instant> = OnceLock::new();
272/// impl<U: Ui> Widget<U> for UpTime {
273/// #     type Cfg = UpTimeCfg<U>;
274/// #     fn cfg() -> Self::Cfg {
275/// #         UpTimeCfg(PhantomData)
276/// #     }
277/// #     fn text(&self) -> &Text {
278/// #         &self.0
279/// #     }
280/// #     fn text_mut(&mut self) -> &mut Text {
281/// #         &mut self.0
282/// #     }
283///     // ...
284///     fn update(&mut self, _area: &U::Area) {
285///         let Some(start) = START_TIME.get() else {
286///             return;
287///         };
288///         let duration = start.elapsed();
289///         let mins = duration.as_secs() / 60;
290///         let secs = duration.as_secs() % 60;
291///         self.0 = text!([UpTime] mins "m " secs "s");
292///     }
293///     // ...
294///     fn once() -> Result<(), duat_core::Error<()>> {
295///         Ok(())
296///     }
297/// }
298/// ```
299///
300/// [`Mode`]: crate::mode::Mode
301/// [`cfg`]: Widget::cfg
302/// [`build`]: WidgetCfg::build
303/// [How]: PushSpecs
304/// [`periodic_checker`]: crate::periodic_checker
305/// [`PhantomData<U>`]: std::marker::PhantomData
306/// [`Instant`]: std::time::Instant
307/// [`ConfigLoaded`]: crate::hooks::ConfigLoaded
308/// [`update`]: Widget::update
309/// [`Form`]: crate::form::Form
310/// [`form::set_weak*`]: crate::form::set_weak
311/// [`text!`]: crate::text::text
312pub trait Widget<U>: Send + Sync + 'static
313where
314    U: Ui,
315{
316    /// The configuration type
317    type Cfg: WidgetCfg<U, Widget = Self>
318    where
319        Self: Sized;
320
321    /// Returns a [`WidgetCfg`], for use in layout construction
322    ///
323    /// This function exists primarily so the [`WidgetCfg`]s
324    /// themselves don't need to be in scope. You will want to use
325    /// these in [hooks] like [`OnFileOpen`]:
326    ///
327    /// ```rust
328    /// # use duat_core::{
329    /// #     hooks::{self, OnFileOpen},
330    /// #     ui::{FileBuilder, Ui},
331    /// #     widgets::{File, LineNumbers, Widget, common::selections_fmt, status},
332    /// # };
333    /// # fn test<U: Ui>() {
334    /// hooks::remove("FileWidgets");
335    /// hooks::add::<OnFileOpen<U>>(|builder| {
336    ///     // Screw it, LineNumbers on both sides.
337    ///     builder.push(LineNumbers::cfg());
338    ///     builder.push(LineNumbers::cfg().on_the_right().align_right());
339    /// });
340    /// # }
341    /// ```
342    ///
343    /// [`OnFileOpen`]: crate::hooks::OnFileOpen
344    fn cfg() -> Self::Cfg
345    where
346        Self: Sized;
347
348    /// Updates the widget, allowing the modification of its [`Area`]
349    ///
350    /// There are a few contexts in which this function is triggered:
351    ///
352    /// * A key was sent to the widget, if it is an [`Widget`]
353    /// * It was modified externally by something like [`IncSearch`]
354    /// * The window was resized, so all widgets must be reprinted
355    ///
356    /// In this function, the text should be updated to match its new
357    /// conditions, or request changes to its [`Area`]. As an example,
358    /// the [`LineNumbers`] widget asks for [more or less width],
359    /// depending on the number of lines in the file, in order
360    /// to show an appropriate number of digits.
361    ///
362    /// [`Session`]: crate::session::Session
363    /// [more or less width]: Area::constrain_hor
364    fn update(&mut self, _area: &U::Area) {}
365
366    /// The text that this widget prints out
367    fn text(&self) -> &Text;
368
369    /// A mutable reference to the [`Text`] that is printed
370    fn text_mut(&mut self) -> &mut Text;
371
372    /// The [`Cursors`] that are used on the [`Text`], if they exist
373    fn cursors(&self) -> Option<&Cursors> {
374        self.text().cursors()
375    }
376
377    /// A mutable reference to the [`Cursors`], if they exist
378    fn cursors_mut(&mut self) -> Option<&mut Cursors> {
379        self.text_mut().cursors_mut()
380    }
381
382    /// Actions to do whenever this [`Widget`] is focused.
383    #[allow(unused)]
384    fn on_focus(&mut self, area: &U::Area) {}
385
386    /// Actions to do whenever this [`Widget`] is unfocused.
387    #[allow(unused)]
388    fn on_unfocus(&mut self, area: &U::Area) {}
389
390    /// The [configuration] for how to print [`Text`]
391    ///
392    /// The default configuration, used when `print_cfg` is not
393    /// implemented,can be found at [`PrintCfg::new`].
394    ///
395    /// [configuration]: PrintCfg
396    fn print_cfg(&self) -> PrintCfg {
397        PrintCfg::new()
398    }
399
400    /// Prints the widget
401    ///
402    /// Very rarely shouuld you actually implement this method, one
403    /// example of where this is actually implemented is in
404    /// [`File::print`], where [`Area::print_with`] is called in order
405    /// to simultaneously update the list of lines numbers, for
406    /// widgets like [`LineNumbers`] to read.
407    fn print(&mut self, area: &U::Area) {
408        let cfg = self.print_cfg();
409        area.print(self.text_mut(), cfg, form::painter())
410    }
411
412    /// Actions taken when this widget opens for the first time
413    ///
414    /// Examples of things that should go in here are [`form`]
415    /// functions, [hooks], [commands] you want executed only once
416    ///
417    /// [commands]: crate::cmd
418    fn once() -> Result<(), crate::Error<()>>
419    where
420        Self: Sized;
421}
422
423/// A configuration struct for a [`Widget`]
424///
425/// This configuration is used to make adjustments on how a widget
426/// will be added to a file or a window. These adjustments are
427/// primarily configurations for the widget itself, and to what
428/// direction it will be pushed:
429///
430/// ```rust
431/// # use duat_core::{
432/// #     hooks::{self, OnFileOpen},
433/// #     ui::Ui,
434/// #     widgets::{LineNumbers, Widget},
435/// # };
436/// # fn test<U: Ui>() {
437/// hooks::add::<OnFileOpen<U>>(|builder| {
438///     // Change pushing direction to the right.
439///     let cfg = LineNumbers::cfg().on_the_right();
440///     // Changes to where the numbers will be placed.
441///     let cfg = cfg.align_right().align_main_left();
442///
443///     builder.push(cfg);
444/// });
445/// # }
446/// ```
447pub trait WidgetCfg<U>: Sized
448where
449    U: Ui,
450{
451    type Widget: Widget<U>;
452
453    fn build(self, on_file: bool) -> (Self::Widget, impl CheckerFn, PushSpecs);
454}
455
456// Elements related to the [`Widget`]s
457pub struct Node<U: Ui> {
458    widget: RwData<dyn Widget<U>>,
459    area: U::Area,
460
461    checker: Arc<dyn Fn() -> bool + Send + Sync>,
462    busy_updating: Arc<AtomicBool>,
463
464    related_widgets: Option<RwData<Vec<Node<U>>>>,
465    on_focus: fn(&Node<U>),
466    on_unfocus: fn(&Node<U>),
467}
468
469impl<U: Ui> Node<U> {
470    pub fn new<W: Widget<U>>(
471        widget: RwData<dyn Widget<U>>,
472        area: U::Area,
473        checker: impl CheckerFn,
474    ) -> Self {
475        fn related_widgets<U: Ui>(
476            widget: &RwData<dyn Widget<U>>,
477            area: &U::Area,
478        ) -> Option<RwData<Vec<Node<U>>>> {
479            widget.mutate_as(|f: &mut File| {
480                let cfg = f.print_cfg();
481                f.text_mut().add_cursors(area, cfg);
482                RwData::default()
483            })
484        }
485        let related_widgets = related_widgets::<U>(&widget, &area);
486
487        Self {
488            widget,
489            area,
490
491            checker: Arc::new(checker),
492            busy_updating: Arc::new(AtomicBool::new(false)),
493
494            related_widgets,
495            on_focus: Self::on_focus_fn::<W>,
496            on_unfocus: Self::on_unfocus_fn::<W>,
497        }
498    }
499
500    pub fn widget(&self) -> &RwData<dyn Widget<U>> {
501        &self.widget
502    }
503
504    /// Returns the downcast ref of this [`Widget`].
505    pub fn try_downcast<W>(&self) -> Option<RwData<W>> {
506        self.widget.try_downcast()
507    }
508
509    pub fn data_is<W: 'static>(&self) -> bool {
510        self.widget.data_is::<W>()
511    }
512
513    pub fn update_and_print(&self) {
514        self.busy_updating.store(true, Ordering::Release);
515
516        let mut widget = self.widget.raw_write();
517        widget.update(&self.area);
518        widget.print(&self.area);
519        drop(widget);
520
521        if let Some(nodes) = &self.related_widgets {
522            for node in &*nodes.read() {
523                if node.needs_update() {
524                    node.update_and_print();
525                }
526            }
527        }
528
529        self.busy_updating.store(false, Ordering::Release);
530    }
531
532    pub fn inspect_as<W: 'static, B>(&self, f: impl FnOnce(&W) -> B) -> Option<B> {
533        self.widget.inspect_as(f)
534    }
535
536    pub fn ptr_eq<W, D>(&self, other: &D) -> bool
537    where
538        W: ?Sized,
539        D: Data<W> + ?Sized,
540    {
541        self.widget.ptr_eq(other)
542    }
543
544    pub fn needs_update(&self) -> bool {
545        if !self.busy_updating.load(Ordering::Acquire) {
546            (self.checker)() || self.area.has_changed()
547        } else {
548            false
549        }
550    }
551
552    pub(crate) fn update(&self) {
553        self.widget.raw_write().update(&self.area)
554    }
555
556    pub(crate) fn parts(&self) -> (&RwData<dyn Widget<U>>, &U::Area) {
557        (&self.widget, &self.area)
558    }
559
560    pub(crate) fn as_file(&self) -> Option<FileParts<U>> {
561        self.widget.try_downcast().map(|file| {
562            (
563                file,
564                self.area.clone(),
565                self.related_widgets.clone().unwrap(),
566            )
567        })
568    }
569
570    pub(crate) fn on_focus(&self) {
571        self.area.set_as_active();
572        (self.on_focus)(self)
573    }
574
575    pub(crate) fn on_unfocus(&self) {
576        (self.on_unfocus)(self)
577    }
578
579    pub(crate) fn raw_inspect<B>(&self, f: impl FnOnce(&dyn Widget<U>) -> B) -> B {
580        let widget = self.widget.raw_read();
581        f(&*widget)
582    }
583
584    pub(crate) fn area(&self) -> &U::Area {
585        &self.area
586    }
587
588    pub(crate) fn related_widgets(&self) -> Option<&RwData<Vec<Node<U>>>> {
589        self.related_widgets.as_ref()
590    }
591
592    fn on_focus_fn<W: Widget<U>>(&self) {
593        self.area.set_as_active();
594        let widget = self.widget.try_downcast().unwrap();
595        hooks::trigger::<FocusedOn<W, U>>((widget, self.area.clone()));
596    }
597
598    fn on_unfocus_fn<W: Widget<U>>(&self) {
599        let widget = self.widget.try_downcast().unwrap();
600        hooks::trigger::<UnfocusedFrom<W, U>>((widget, self.area.clone()));
601    }
602}
603
604impl<U: Ui> Clone for Node<U> {
605    fn clone(&self) -> Self {
606        Self {
607            widget: self.widget.clone(),
608            area: self.area.clone(),
609            checker: self.checker.clone(),
610            busy_updating: self.busy_updating.clone(),
611            related_widgets: self.related_widgets.clone(),
612            on_focus: self.on_focus,
613            on_unfocus: self.on_unfocus,
614        }
615    }
616}
617
618impl<U: Ui> PartialEq for Node<U> {
619    fn eq(&self, other: &Self) -> bool {
620        self.widget.ptr_eq(&other.widget)
621    }
622}
623
624impl<U: Ui> Eq for Node<U> {}
625
626pub trait CheckerFn = Fn() -> bool + 'static + Send + Sync;