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