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