Skip to main content

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, BufferPrinted, 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/// [`Mode`]: crate::mode::Mode
63#[allow(unused)]
64pub trait Widget: Send + 'static {
65    ////////// Stateful functions
66
67    /// Updates the widget alongside its [`RwArea`] in the [`Handle`]
68    ///
69    /// This function will be triggered when Duat deems that a change
70    /// has happened to this [`Widget`], which is when
71    /// [`Widget::needs_update`] returns `true`.
72    ///
73    /// It can also happen if [`RwData<Self>::has_changed`] or
74    /// [`RwData::has_changed`] return `true`. This can happen
75    /// from many places, like [hooks], [commands], editing by
76    /// [`Mode`]s, etc.
77    ///
78    /// Importantly, [`update`] should be able to handle any number of
79    /// changes that have taken place in this [`Widget`], so you
80    /// should avoid depending on state which may become
81    /// desynchronized.
82    ///
83    /// When implementing this, you are free to remove the `where`
84    /// clause.
85    ///
86    /// [hooks]: crate::hook
87    /// [commands]: crate::cmd
88    /// [`Mode`]: crate::mode::Mode
89    /// [`update`]: Widget::update
90    fn update(pa: &mut Pass, handle: &Handle<Self>)
91    where
92        Self: Sized;
93
94    /// Actions to do whenever this [`Widget`] is focused
95    ///
96    /// When implementing this, you are free to remove the `where`
97    /// clause.
98    fn on_focus(pa: &mut Pass, handle: &Handle<Self>)
99    where
100        Self: Sized,
101    {
102    }
103
104    /// Actions to do whenever this [`Widget`] is unfocused
105    ///
106    /// When implementing this, you are free to remove the `where`
107    /// clause.
108    fn on_unfocus(pa: &mut Pass, handle: &Handle<Self>)
109    where
110        Self: Sized,
111    {
112    }
113
114    /// How to handle a [`MouseEvent`]
115    ///
116    /// Normally, nothing will be done, with the exception of button
117    /// [`Tag`]s which are triggered normally.
118    ///
119    /// [`Tag`]: crate::text::Tag
120    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent)
121    where
122        Self: Sized,
123    {
124    }
125
126    /// Tells Duat that this [`Widget`] should be updated
127    ///
128    /// Determining wether a [`Widget`] should be updated, for a good
129    /// chunk of them, will require some code like this:
130    ///
131    /// ```rust
132    /// # duat_core::doc_duat!(duat);
133    /// use duat::prelude::*;
134    ///
135    /// struct MyWidget(Handle<Buffer>);
136    ///
137    /// impl Widget for MyWidget {
138    /// #   fn update(_: &mut Pass, handle: &Handle<Self>) { todo!() }
139    /// #   fn text(&self) -> &Text { todo!() }
140    /// #   fn text_mut(&mut self) -> TextMut<'_> { todo!() }
141    ///     // ...
142    ///     fn needs_update(&self, pa: &Pass) -> bool {
143    ///         self.0.has_changed(pa)
144    ///     }
145    /// }
146    /// ```
147    ///
148    /// In this case, `MyWidget` is telling Duat that it should be
149    /// updated whenever the buffer in the [`Handle<Buffer>`] gets
150    /// changed.
151    ///
152    /// One interesting use case of this function is the
153    /// [`StatusLine`], which can be altered if any of its parts
154    /// get changed, some of them depend on a [`Handle<Buffer>`],
155    /// but a lot of others depend on checking other [`data`] types in
156    /// order to figure out if an update is needed.
157    ///
158    /// [`StatusLine`]: https://docs.rs/duat-core/latest/duat/widgets/struct.StatusLine.html
159    /// [`data`]: crate::data
160    fn needs_update(&self, pa: &Pass) -> bool;
161
162    /// The text that this widget prints out
163    fn text(&self) -> &Text;
164
165    /// A mutable reference to the [`Text`] that is printed
166    fn text_mut(&mut self) -> TextMut<'_>;
167
168    /// The [configuration] for how to print [`Text`]
169    ///
170    /// The default configuration, used when `print_opts` is not
171    /// implemented,can be found at [`PrintOpts::new`].
172    ///
173    /// [configuration]: PrintOpts
174    fn print_opts(&self) -> PrintOpts {
175        PrintOpts::new()
176    }
177}
178
179/// Elements related to the [`Widget`]s
180#[derive(Clone)]
181pub(crate) struct Node {
182    handle: Handle<dyn Widget>,
183    update: Arc<dyn Fn(&mut Pass) + Send + Sync>,
184    print: Arc<dyn Fn(&mut Pass, &Handle<dyn Widget>) + Send + Sync>,
185    on_focus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
186    on_unfocus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
187    on_mouse_event: Arc<dyn Fn(&mut Pass, UiMouseEvent) + Send + Sync>,
188}
189
190impl Node {
191    /// Returns a new `Node`
192    pub(crate) fn new<W: Widget>(
193        widget: RwData<W>,
194        area: RwArea,
195        master: Option<Handle<dyn Widget>>,
196    ) -> Self {
197        Self::from_handle(Handle::new(widget, area, Arc::new(Mutex::new("")), master))
198    }
199
200    /// Returns a `Node` from an existing [`Handle`]
201    pub(crate) fn from_handle<W: Widget>(handle: Handle<W>) -> Self {
202        Self {
203            handle: handle.to_dyn(),
204            update: Arc::new({
205                let handle = handle.clone();
206                move |pa| _ = catch_panic(|| W::update(pa, &handle))
207            }),
208            print: Arc::new({
209                let handle = handle.clone();
210                let buf_handle = handle.try_downcast();
211
212                move |pa, orig_handle| {
213                    let painter =
214                        form::painter_with_widget_and_mask::<W>(*handle.mask().lock().unwrap());
215
216                    handle
217                        .area
218                        .print(pa, handle.text(pa), handle.opts(pa), painter);
219
220                    if let Some(buf_handle) = buf_handle.clone() {
221                        hook::trigger(pa, BufferPrinted(buf_handle));
222                        orig_handle.declare_as_read();
223                        orig_handle.area().0.declare_as_read();
224                    }
225                }
226            }),
227            on_focus: Arc::new({
228                let handle = handle.clone();
229                move |pa, old| {
230                    hook::trigger(pa, FocusedOn((old, handle.clone())));
231                    catch_panic(|| W::on_focus(pa, &handle));
232                }
233            }),
234            on_unfocus: Arc::new({
235                let handle = handle.clone();
236                move |pa, new| {
237                    hook::trigger(pa, UnfocusedFrom((handle.clone(), new)));
238                    catch_panic(|| W::on_unfocus(pa, &handle));
239                }
240            }),
241            on_mouse_event: Arc::new({
242                let dyn_handle = handle.to_dyn();
243                let handle = handle.clone();
244                move |pa, event| {
245                    let opts = handle.opts(pa);
246                    let text = handle.text(pa);
247                    let event = MouseEvent {
248                        points: handle.area().points_at_coord(pa, text, event.coord, opts),
249                        coord: event.coord,
250                        kind: event.kind,
251                        modifiers: event.modifiers,
252                    };
253
254                    catch_panic(|| {
255                        hook::trigger(pa, OnMouseEvent((dyn_handle.clone(), event)));
256                        W::on_mouse_event(pa, &handle, event);
257                    });
258                }
259            }),
260        }
261    }
262
263    ////////// Reading and parts acquisition
264
265    pub(crate) fn read_as<'a, W: Widget>(&'a self, pa: &'a Pass) -> Option<&'a W> {
266        self.handle.read_as(pa)
267    }
268
269    /// The [`Widget`] of this [`Node`]
270    pub(crate) fn widget(&self) -> &RwData<dyn Widget> {
271        self.handle.widget()
272    }
273
274    /// The [`Ui::Area`] of this [`Widget`]
275    pub(crate) fn area(&self) -> &RwArea {
276        self.handle.area()
277    }
278
279    /// Returns the downcast ref of this [`Widget`].
280    pub(crate) fn try_downcast<W: Widget>(&self) -> Option<Handle<W>> {
281        self.handle.try_downcast()
282    }
283
284    /// The "parts" of this [`Node`]
285    pub(crate) fn handle(&self) -> &Handle<dyn Widget> {
286        &self.handle
287    }
288
289    ////////// Querying functions
290
291    /// Wether the value within is `W`
292    pub(crate) fn data_is<W: 'static>(&self) -> bool {
293        self.handle.widget().is::<W>()
294    }
295
296    /// Wether this and [`RwData`] point to the same value
297    pub(crate) fn ptr_eq<W: ?Sized>(&self, other: &RwData<W>) -> bool {
298        self.handle.ptr_eq(other)
299    }
300
301    /// Wether this [`Widget`] needs to be updated
302    pub(crate) fn needs_update(&self, pa: &Pass) -> bool {
303        self.handle.update_requested.load(Ordering::Relaxed)
304            || self.handle.widget().has_changed()
305            || self.handle.area.has_changed(pa)
306            || self.handle.read(pa).needs_update(pa)
307    }
308
309    ////////// Eventful functions
310
311    /// Updates and prints this [`Node`]
312    pub(crate) fn update_and_print(&self, pa: &mut Pass, win: usize) {
313        self.handle.update_requested.store(false, Ordering::Relaxed);
314        if self.handle().is_closed(pa) {
315            return;
316        }
317
318        (self.update)(pa);
319
320        crate::context::windows().cleanup_despawned(pa);
321        if self.handle().is_closed(pa) {
322            return;
323        }
324
325        let print_info = self.handle.area().get_print_info(pa);
326        let (widget, _) = self.handle.write_with_area(pa);
327
328        if print_info != PrintInfo::default() {
329            widget.text_mut().update_bounds();
330        }
331
332        let widgets_to_spawn = widget.text_mut().get_widget_spawns();
333        for spawn in widgets_to_spawn {
334            spawn(pa, win, self.handle.clone());
335        }
336
337        (self.print)(pa, &self.handle);
338    }
339
340    /// What to do when focusing
341    pub(crate) fn on_focus(&self, pa: &mut Pass, old: Handle<dyn Widget>) {
342        self.handle.area().set_as_active(pa);
343        (self.on_focus)(pa, old)
344    }
345
346    /// What to do when unfocusing
347    pub(crate) fn on_unfocus(&self, pa: &mut Pass, new: Handle<dyn Widget>) {
348        (self.on_unfocus)(pa, new)
349    }
350
351    pub(crate) fn on_mouse_event(&self, pa: &mut Pass, mouse_event: UiMouseEvent) {
352        (self.on_mouse_event)(pa, mouse_event);
353    }
354}
355
356impl std::fmt::Debug for Node {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        f.debug_struct("Node")
359            .field("handle", &self.handle)
360            .finish_non_exhaustive()
361    }
362}