duat_core/ui/
widget.rs

1//! APIs for the construction of [`Widget`]s
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//! These widgets will be used in a few circumstances:
8//!
9//! - Pushed to [`Handle`]s via [`Handle::push_outer_widget`] or
10//!   [`Handle::push_inner_widget`].
11//! - Pushed to the [`Window`]'s edges of a window via
12//!   [`Window::push_inner`] and [`Window::push_outer`].
13//! - Spawned on [`Handle`]s via [`Handle::spawn_widget`].
14//! - Spawned on [`Text`] via the [`SpawnTag`] [tag].
15//!
16//! They can be pushed to all 4 sides of other widgets through the
17//! use of [`PushSpecs`]. Or they can be spawned with
18//! [`DynSpawnSpecs`]. Each of these structs determine the specifics
19//! of where the [`Widget`] will be spawned, as well as how its
20//! [`Area`] should adapt to changes in the layout.
21//!
22//! For example, if you spawn a [`Widget`] on [`Text`] via the
23//! [`SpawnTag`], then any movements and modifications on said `Text`
24//! will also move the `Widget` around.
25//!
26//! The only [`Widget`] that is defined in `duat-core` is the
27//! [`Buffer`]. It is the quitessential `Widget` for a text editor,
28//! being the part that is modified by user input.
29//!
30//! [`Buffer`]: crate::buffer::Buffer
31//! [`Window`]: super::Window
32//! [`Window::push_inner`]: super::Window::push_inner
33//! [`Window::push_outer`]: super::Window::push_outer
34//! [`SpawnTag`]: crate::text::SpawnTag
35//! [tag]: crate::text::Tag
36//! [`PushSpecs`]: super::PushSpecs
37//! [`DynSpawnSpecs`]: super::DynSpawnSpecs
38//! [`Area`]: super::Area
39use std::sync::{Arc, Mutex};
40
41use crossterm::event::MouseEvent;
42
43use crate::{
44    context::Handle,
45    data::{Pass, RwData},
46    form::{self, Painter},
47    hook::{self, FocusedOn, UnfocusedFrom},
48    opts::PrintOpts,
49    text::Text,
50    ui::{Coord, PrintInfo, RwArea},
51};
52
53/// An area where [`Text`] will be printed to the screen
54///
55/// Most widgets are supposed to be passive widgets, that simply show
56/// information about the current state of Duat. In order to
57/// show that information, widgets make use of [`Text`], which can
58/// show stylized text, buttons, and all sorts of other stuff. (For
59/// widgets that react to input, see the documentation for[`Mode`]).
60///
61/// For a demonstration on how to create a widget, I will create a
62/// widget that shows the uptime, in seconds, for Duat.
63///
64/// ```rust
65/// # duat_core::doc_duat!(duat);
66/// use std::time::Duration;
67///
68/// use duat::{data::PeriodicChecker, prelude::*};
69///
70/// struct UpTime(Text, PeriodicChecker);
71///
72/// impl UpTime {
73///     fn new() -> Self {
74///         Self(
75///             Text::default(),
76///             PeriodicChecker::new(Duration::from_secs(1)),
77///         )
78///     }
79/// }
80/// ```
81///
82/// In order to be a proper widget, it must have a [`Text`] to
83/// display. The [`PeriodicChecker`] will be explained later. Next, I
84/// implement `Widget` on `UpTime`:
85///
86/// ```rust
87/// # duat_core::doc_duat!(duat);
88/// use std::time::Duration;
89///
90/// use duat::{data::PeriodicChecker, prelude::*};
91///
92/// struct UpTime(Text, PeriodicChecker);
93///
94/// impl UpTime {
95///     fn new() -> Self {
96///         Self(
97///             Text::default(),
98///             PeriodicChecker::new(Duration::from_secs(1)),
99///         )
100///     }
101/// }
102///
103/// impl Widget for UpTime {
104///     fn update(pa: &mut Pass, handle: &Handle<Self>) {
105///         todo!()
106///     }
107///
108///     fn needs_update(&self, pa: &Pass) -> bool {
109///         todo!();
110///     }
111///
112///     fn text(&self) -> &Text {
113///         &self.0
114///     }
115///
116///     fn text_mut(&mut self) -> &mut Text {
117///         &mut self.0
118///     }
119/// }
120/// ```
121///
122/// The [`Widget::update`] funcion is responsible for updating the
123/// [`Text`] of the `Widget` on every frame. However, it is only
124/// called if [`Widget::needs_update`] returns `true`. Note that this
125/// isn't the only way to update `Widget`s, since in any place where
126/// you have global access throught the [`Pass`] (like [hooks],
127/// [commands], etc.), you can update any [`Handle`] for any `Widget`.
128///
129/// There are two other `Widget` functions:
130/// [`Widget::on_focus`] and [`Widget::on_unfocus`], which are called
131/// when a [`Mode`] is set, and the `Widget` is focused or unfocused.
132/// For this example, since there are no `Mode`s, these will not be
133/// used.
134///
135/// Next, I will finish implementing the `Widget` trait.
136///
137/// First of all, there needs to be a starting [`Instant`] to compare
138/// with the current moment in time. The correct moment to do that
139/// would be right as the `setup` function is called. This can be done
140/// safely with a [`OnceLock`]:
141///
142/// ```rust
143/// # duat_core::doc_duat!(duat);
144/// setup_duat!(setup);
145/// use std::{sync::OnceLock, time::Instant};
146///
147/// use duat::prelude::*;
148/// static START_TIME: OnceLock<Instant> = OnceLock::new();
149///
150/// fn setup() {
151///     START_TIME.set(Instant::now()).unwrap();
152/// }
153/// ```
154///
155/// However, exposing that to end users is rather poor UX, so you
156/// should make use of [`Plugin`]s instead:
157///
158/// ```rust
159/// # duat_core::doc_duat!(duat);
160/// use std::{sync::OnceLock, time::Instant};
161///
162/// use duat::prelude::*;
163/// struct UpTimePlugin;
164/// static START_TIME: OnceLock<Instant> = OnceLock::new();
165///
166/// impl Plugin for UpTimePlugin {
167///     fn plug(self, plugins: &Plugins) {
168///         START_TIME.set(Instant::now()).unwrap();
169///     }
170/// }
171/// ```
172///
173/// Next, I'm going to implement the [`update`] function:
174///
175/// ```rust
176/// # use std::{sync::OnceLock, time::Instant};
177/// # duat_core::doc_duat!(duat);
178/// use duat::{data::PeriodicChecker, prelude::*};
179/// # struct UpTime(Text, PeriodicChecker);
180///
181/// static START_TIME: OnceLock<Instant> = OnceLock::new();
182///
183/// impl Widget for UpTime {
184/// #     fn text(&self) -> &Text { &self.0 }
185/// #     fn text_mut(&mut self) -> &mut Text { &mut self.0 }
186/// #     fn needs_update(&self, pa: &Pass) -> bool { todo!(); }
187///     // ...
188///     fn update(pa: &mut Pass, handle: &Handle<Self>) {
189///         let start = START_TIME.get().unwrap();
190///         let elapsed = start.elapsed();
191///         let mins = elapsed.as_secs() / 60;
192///         let secs = elapsed.as_secs() % 60;
193///
194///         handle.write(pa).0 = txt!("[uptime.mins]{mins}m [uptime.secs]{secs}s");
195///     }
196/// }
197/// ```
198///
199/// This should format the [`Text`] via [`txt!`] to show how many
200/// minutes and seconds have passed. However, I'm using the
201/// `uptime.mins` and `updime.secs` [`Form`]s, which aren't set to
202/// anything, so they'll just display normally colored text.
203///
204/// To solve that, just add more statements to the plugin:
205///
206/// ```rust
207/// # duat_core::doc_duat!(duat);
208/// # use std::{sync::OnceLock, time::Instant};
209/// use duat::prelude::*;
210///
211/// struct UpTimePlugin;
212///
213/// static START_TIME: OnceLock<Instant> = OnceLock::new();
214///
215/// impl Plugin for UpTimePlugin {
216///     fn plug(self, plugins: &Plugins) {
217///         START_TIME.set(Instant::now()).unwrap();
218///         form::set_weak("uptime", Form::green());
219///     }
220/// }
221/// ```
222///
223/// Note the [`form::set_weak`]. This function "weakly" sets the
224/// [`Form`], that is, it sets it _only_ if it wasn't set before via
225/// [`form::set`]. This is useful since the order in which the plugin
226/// is added and the `Form` is set by the end user doesn't end up
227/// mattering.
228///
229/// Note also that I set `uptime`, rather than `uptime.mins` or
230/// `uptime.secs`. Due to `Form` inheritance, any form with a `.` in
231/// it will inherit from the parent, unless explicitly set to
232/// something else. this inheritance follows even when the parent
233/// changes. That is, if the user sets the `uptime` form to something
234/// else, `uptime.mins` and `uptime.secs` will also be set to that.
235///
236/// Now, I'm going to implement the [`needs_update`] function, that's
237/// where the [`PeriodicChecker`] comes in to play:
238///
239/// ```rust
240/// # duat_core::doc_duat!(duat);
241/// # use std::{sync::OnceLock, time::Instant};
242/// use duat::{data::PeriodicChecker, prelude::*};
243///
244/// // This was set during the `setup` function
245/// static START_TIME: OnceLock<Instant> = OnceLock::new();
246///
247/// struct UpTime(Text, PeriodicChecker);
248///
249/// impl Widget for UpTime {
250/// #     fn text(&self) -> &Text { &self.0 }
251/// #     fn text_mut(&mut self) -> &mut Text { &mut self.0 }
252/// #     fn update(pa: &mut Pass, handle: &Handle<Self>) { }
253///     fn needs_update(&self, pa: &Pass) -> bool {
254///         // Returns `true` once per second
255///         self.1.check()
256///     }
257/// }
258/// ```
259///
260/// The [`needs_update`] function is executed on every frame, however,
261/// it should only return `true` every second, which is when the
262/// [`update`] function will be called, updating the `Widget`.
263///
264/// Now, all that is left to do is placing the `Widget` on screen. To
265/// do that, I will make use of a [hook] to place them on the bottom
266/// of the [`Window`], right below the [`Buffer`]s:
267///
268/// ```rust
269/// # duat_core::doc_duat!(duat);
270/// use std::{
271///     sync::OnceLock,
272///     time::{Duration, Instant},
273/// };
274///
275/// use duat::{data::PeriodicChecker, prelude::*};
276///
277/// static START_TIME: OnceLock<Instant> = OnceLock::new();
278///
279/// struct UpTimePlugin;
280///
281/// impl Plugin for UpTimePlugin {
282///     fn plug(self, plugins: &Plugins) {
283///         START_TIME.set(Instant::now()).unwrap();
284///         form::set_weak("uptime", Form::green());
285///
286///         hook::add::<WindowCreated>(|pa, window| {
287///             let specs = ui::PushSpecs {
288///                 side: ui::Side::Below,
289///                 height: Some(1.0),
290///                 ..Default::default()
291///             };
292///             window.push_inner(pa, UpTime::new(), specs);
293///             Ok(())
294///         });
295///     }
296/// }
297///
298/// struct UpTime(Text, PeriodicChecker);
299///
300/// impl UpTime {
301///     fn new() -> Self {
302///         Self(
303///             Text::default(),
304///             PeriodicChecker::new(Duration::from_secs(1)),
305///         )
306///     }
307/// }
308///
309/// impl Widget for UpTime {
310///     fn update(pa: &mut Pass, handle: &Handle<Self>) {
311///         let start = START_TIME.get().unwrap();
312///         let elapsed = start.elapsed();
313///         let mins = elapsed.as_secs() / 60;
314///         let secs = elapsed.as_secs() % 60;
315///
316///         handle.write(pa).0 = txt!("[uptime.mins]{mins}m [uptime.secs]{secs}s");
317///     }
318///
319///     fn needs_update(&self, pa: &Pass) -> bool {
320///         self.1.check()
321///     }
322///
323///     fn text(&self) -> &Text {
324///         &self.0
325///     }
326///
327///     fn text_mut(&mut self) -> &mut Text {
328///         &mut self.0
329///     }
330/// }
331/// ```
332///
333/// Here, I'm adding a [hook] to push this widget to the bottom of the
334/// [`Window`], right as said [`Window`] is opened. By using
335/// [`Window::push_inner`], the `Widget` will be placed below the
336/// central [`Buffer`]s region, but above other `Widget`s that were
337/// pushed to the bottom. If I wanted the `Widget` on the edges of the
338/// screen, I could use [`Window::push_outer` instead.
339///
340/// [`Mode`]: crate::mode::Mode
341/// [`PeriodicChecker`]: crate::data::PeriodicChecker
342/// [`WidgetCreated`]: crate::hook::WidgetCreated
343/// [`WindowCreated`]: crate::hook::WindowCreated
344/// [hooks]: crate::hook
345/// [commands]: crate::cmd
346/// [`PhantomData<U>`]: std::marker::PhantomData
347/// [`Instant`]: std::time::Instant
348/// [`ConfigLoaded`]: crate::hook::ConfigLoaded
349/// [`update`]: Widget::update
350/// [`needs_update`]: Widget::needs_update
351/// [`Form`]: crate::form::Form
352/// [`form::set_weak*`]: crate::form::set_weak
353/// [`txt!`]: crate::text::txt
354/// [`Plugin`]: crate::Plugin
355/// [`Buffer`]: crate::buffer::Buffer
356/// [`PushSpecs`]: super::PushSpecs
357/// [`Window`]: super::Window
358/// [`Window::push_inner`]: super::Window::push_inner
359/// [`Window::push_outer`]: super::Window::push_outer
360/// [`OnceLock`]: std::sync::OnceLock
361pub trait Widget: Send + 'static {
362    ////////// Stateful functions
363
364    /// Updates the widget alongside its [`RwArea`] in the [`Handle`]
365    ///
366    /// This function will be triggered when Duat deems that a change
367    /// has happened to this [`Widget`], which is when
368    /// [`Widget::needs_update`] returns `true`.
369    ///
370    /// It can also happen if [`RwData<Self>::has_changed`] or
371    /// [`RwData::has_changed`] return `true`. This can happen
372    /// from many places, like [hooks], [commands], editing by
373    /// [`Mode`]s, etc.
374    ///
375    /// Importantly, [`update`] should be able to handle any number of
376    /// changes that have taken place in this [`Widget`], so you
377    /// should avoid depending on state which may become
378    /// desynchronized.
379    ///
380    /// When implementing this, you are free to remove the `where`
381    /// clause.
382    ///
383    /// [hooks]: crate::hook
384    /// [commands]: crate::cmd
385    /// [`Mode`]: crate::mode::Mode
386    /// [`update`]: Widget::update
387    #[allow(unused)]
388    fn update(pa: &mut Pass, handle: &Handle<Self>)
389    where
390        Self: Sized;
391
392    /// Actions to do whenever this [`Widget`] is focused
393    ///
394    /// When implementing this, you are free to remove the `where`
395    /// clause.
396    #[allow(unused)]
397    fn on_focus(pa: &mut Pass, handle: &Handle<Self>)
398    where
399        Self: Sized,
400    {
401    }
402
403    /// Actions to do whenever this [`Widget`] is unfocused
404    ///
405    /// When implementing this, you are free to remove the `where`
406    /// clause.
407    #[allow(unused)]
408    fn on_unfocus(pa: &mut Pass, handle: &Handle<Self>)
409    where
410        Self: Sized,
411    {
412    }
413
414    /// Tells Duat that this [`Widget`] should be updated
415    ///
416    /// Determining wether a [`Widget`] should be updated, for a good
417    /// chunk of them, will require some code like this:
418    ///
419    /// ```rust
420    /// # duat_core::doc_duat!(duat);
421    /// use duat::prelude::*;
422    ///
423    /// struct MyWidget(Handle<Buffer>);
424    ///
425    /// impl Widget for MyWidget {
426    /// #   fn update(_: &mut Pass, handle: &Handle<Self>) { todo!() }
427    /// #   fn text(&self) -> &Text { todo!() }
428    /// #   fn text_mut(&mut self) -> &mut Text { todo!() }
429    ///     // ...
430    ///     fn needs_update(&self, pa: &Pass) -> bool {
431    ///         self.0.has_changed(pa)
432    ///     }
433    /// }
434    /// ```
435    ///
436    /// In this case, `MyWidget` is telling Duat that it should be
437    /// updated whenever the buffer in the [`Handle<Buffer>`] gets
438    /// changed.
439    ///
440    /// One interesting use case of this function is the
441    /// [`StatusLine`], which can be altered if any of its parts
442    /// get changed, some of them depend on a [`Handle<Buffer>`],
443    /// but a lot of others depend on checking other [`data`] types in
444    /// order to figure out if an update is needed.
445    ///
446    /// [`StatusLine`]: https://docs.rs/duat-core/latest/duat/widgets/struct.StatusLine.html
447    /// [`data`]: crate::data
448    fn needs_update(&self, pa: &Pass) -> bool;
449
450    /// The text that this widget prints out
451    fn text(&self) -> &Text;
452
453    /// A mutable reference to the [`Text`] that is printed
454    fn text_mut(&mut self) -> &mut Text;
455
456    /// The [configuration] for how to print [`Text`]
457    ///
458    /// The default configuration, used when `print_opts` is not
459    /// implemented,can be found at [`PrintOpts::new`].
460    ///
461    /// [configuration]: PrintOpts
462    fn get_print_opts(&self) -> PrintOpts {
463        PrintOpts::new()
464    }
465
466    /// Prints the widget
467    ///
468    /// Very rarely shouuld you actually implement this method, one
469    /// example of where this is actually implemented is in
470    /// [`Buffer::print`], where [`RwArea::print_with`] is called in
471    /// order to simultaneously update the list of lines numbers,
472    /// for widgets like [`LineNumbers`] to read.
473    ///
474    /// [`LineNumbers`]: docs.rs/duat/latest/duat/widgets/struct.LineNumbers.html
475    /// [`Buffer::print`]: crate::buffer::Buffer::print
476    fn print(&self, pa: &Pass, painter: Painter, area: &RwArea) {
477        let opts = self.get_print_opts();
478        area.print(pa, self.text(), opts, painter)
479    }
480}
481
482/// Elements related to the [`Widget`]s
483#[derive(Clone)]
484pub(crate) struct Node {
485    handle: Handle<dyn Widget>,
486    update: Arc<dyn Fn(&mut Pass) + Send + Sync>,
487    print: Arc<dyn Fn(&mut Pass) + Send + Sync>,
488    on_focus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
489    on_unfocus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
490    on_mouse_event: Arc<dyn Fn(&mut Pass, Coord, MouseEvent) -> Option<MouseEvent> + Send + Sync>,
491}
492
493impl Node {
494    /// Returns a new `Node`
495    pub(crate) fn new<W: Widget>(
496        widget: RwData<W>,
497        area: RwArea,
498        master: Option<Handle<dyn Widget>>,
499    ) -> Self {
500        Self::from_handle(Handle::new(widget, area, Arc::new(Mutex::new("")), master))
501    }
502
503    /// Returns a `Node` from an existing [`Handle`]
504    pub(crate) fn from_handle<W: Widget>(handle: Handle<W>) -> Self {
505        Self {
506            handle: handle.to_dyn(),
507            update: Arc::new({
508                let handle = handle.clone();
509                move |pa| W::update(pa, &handle)
510            }),
511            print: Arc::new({
512                let handle = handle.clone();
513                move |pa| {
514                    let painter = form::painter_with_mask::<W>(*handle.mask().lock().unwrap());
515                    W::print(handle.read(pa), pa, painter, handle.area());
516                }
517            }),
518            on_focus: Arc::new({
519                let handle = handle.clone();
520                move |pa, old| {
521                    hook::trigger(pa, FocusedOn((old, handle.clone())));
522                    W::on_focus(pa, &handle);
523                }
524            }),
525            on_unfocus: Arc::new({
526                let handle = handle.clone();
527                move |pa, new| {
528                    hook::trigger(pa, UnfocusedFrom((handle.clone(), new)));
529                    W::on_unfocus(pa, &handle);
530                }
531            }),
532            on_mouse_event: Arc::new({
533                move |_pa, _coord, _mouse_event| {
534                    todo!();
535                }
536            }),
537        }
538    }
539
540    ////////// Reading and parts acquisition
541
542    pub(crate) fn read_as<'a, W: Widget>(&'a self, pa: &'a Pass) -> Option<&'a W> {
543        self.handle.read_as(pa)
544    }
545
546    /// The [`Widget`] of this [`Node`]
547    pub(crate) fn widget(&self) -> &RwData<dyn Widget> {
548        self.handle.widget()
549    }
550
551    /// The [`Ui::Area`] of this [`Widget`]
552    pub(crate) fn area(&self) -> &RwArea {
553        self.handle.area()
554    }
555
556    /// Returns the downcast ref of this [`Widget`].
557    pub(crate) fn try_downcast<W: Widget>(&self) -> Option<Handle<W>> {
558        self.handle.try_downcast()
559    }
560
561    /// The "parts" of this [`Node`]
562    pub(crate) fn handle(&self) -> &Handle<dyn Widget> {
563        &self.handle
564    }
565
566    ////////// Querying functions
567
568    /// Wether the value within is `W`
569    pub(crate) fn data_is<W: 'static>(&self) -> bool {
570        self.handle.widget().is::<W>()
571    }
572
573    /// Wether this and [`RwData`] point to the same value
574    pub(crate) fn ptr_eq<W: ?Sized>(&self, other: &RwData<W>) -> bool {
575        self.handle.ptr_eq(other)
576    }
577
578    /// Wether this [`Widget`] needs to be updated
579    pub(crate) fn needs_update(&self, pa: &Pass) -> bool {
580        self.handle.has_changed(pa) || self.handle.read(pa).needs_update(pa)
581    }
582
583    ////////// Eventful functions
584
585    /// Updates and prints this [`Node`]
586    pub(crate) fn update_and_print(&self, pa: &mut Pass, win: usize) {
587        if self.handle().is_closed(pa) {
588            return;
589        }
590
591        (self.update)(pa);
592
593        let print_info = self.handle.area().get_print_info(pa);
594        let (widget, area) = self.handle.write_with_area(pa);
595        let opts = widget.get_print_opts();
596        widget.text_mut().add_selections(area, opts);
597
598        if print_info != PrintInfo::default() {
599            widget.text_mut().update_bounds();
600        }
601
602        let widgets_to_spawn = self.handle.text_mut(pa).get_widget_spawns();
603        for spawn in widgets_to_spawn {
604            spawn(pa, win, self.handle.clone());
605        }
606
607        (self.print)(pa);
608
609        self.handle.text_mut(pa).remove_selections();
610    }
611
612    /// What to do when focusing
613    pub(crate) fn on_focus(&self, pa: &mut Pass, old: Handle<dyn Widget>) {
614        self.handle.area().set_as_active(pa);
615        (self.on_focus)(pa, old)
616    }
617
618    /// What to do when unfocusing
619    pub(crate) fn on_unfocus(&self, pa: &mut Pass, new: Handle<dyn Widget>) {
620        (self.on_unfocus)(pa, new)
621    }
622
623    pub(crate) fn on_mouse_event(&self, pa: &mut Pass, coord: Coord, mouse_event: MouseEvent) {
624        (self.on_mouse_event)(pa, coord, mouse_event);
625    }
626}
627
628impl std::fmt::Debug for Node {
629    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630        f.debug_struct("Node")
631            .field("handle", &self.handle)
632            .finish_non_exhaustive()
633    }
634}