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 [`Spawn`] [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//! [`Spawn`], 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//! [`Window`]: super::Window
31//! [`Window::push_inner`]: super::Window::push_inner
32//! [`Window::push_outer`]: super::Window::push_outer
33//! [`Spawn`]: crate::text::Spawn
34//! [tag]: crate::text::Tag
35//! [`PushSpecs`]: super::PushSpecs
36//! [`DynSpawnSpecs`]: super::DynSpawnSpecs
37//! [`Area`]: super::Area
38use std::sync::{
39    Arc,
40    atomic::{AtomicBool, Ordering},
41};
42
43use crate::{
44    buffer::Buffer,
45    context::Handle,
46    data::{Pass, RwData},
47    form,
48    hook::{self, BufferPrinted, FocusedOn, OnMouseEvent, UnfocusedFrom},
49    mode::{ToggleEvent, TwoPointsPlace},
50    opts::PrintOpts,
51    session::UiMouseEvent,
52    text::{Text, TextMut},
53    ui::{PrintInfo, RwArea},
54};
55
56/// An area where [`Text`] will be printed to the screen
57///
58/// Most widgets are supposed to be passive widgets, that simply show
59/// information about the current state of Duat. In order to
60/// show that information, widgets make use of [`Text`], which can
61/// show stylized text, buttons, and all sorts of other stuff. (For
62/// widgets that react to input, see the documentation for[`Mode`]).
63///
64/// [`Mode`]: crate::mode::Mode
65#[allow(unused)]
66pub trait Widget: Send + 'static {
67    /// The text that this widget prints out
68    fn text(&self) -> &Text;
69
70    /// A mutable reference to the [`Text`] that is printed
71    fn text_mut(&mut self) -> TextMut<'_>;
72
73    /// The [configuration] for how to print [`Text`]
74    ///
75    /// The default configuration, used when `print_opts` is not
76    /// implemented,can be found at [`PrintOpts::new`].
77    ///
78    /// [configuration]: PrintOpts
79    fn print_opts(&self) -> PrintOpts {
80        PrintOpts::new()
81    }
82}
83
84/// Elements related to the [`Widget`]s
85#[derive(Clone)]
86pub(crate) struct Node {
87    handle: Handle<dyn Widget>,
88    print: Arc<dyn Fn(&mut Pass, &Handle<dyn Widget>) + Send + Sync>,
89    on_focus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
90    on_unfocus: Arc<dyn Fn(&mut Pass, Handle<dyn Widget>) + Send + Sync>,
91    on_mouse_event: Arc<dyn Fn(&mut Pass, UiMouseEvent) + Send + Sync>,
92}
93
94impl Node {
95    /// Returns a new `Node`
96    pub(crate) fn new<W: Widget>(
97        widget: RwData<W>,
98        area: RwArea,
99        master: Option<Handle<dyn Widget>>,
100        is_closed: Arc<AtomicBool>,
101    ) -> Self {
102        Self::from_handle(Handle::new(widget, area, master, is_closed))
103    }
104
105    /// Returns a `Node` from an existing [`Handle`]
106    pub(crate) fn from_handle<W: Widget>(handle: Handle<W>) -> Self {
107        Self {
108            handle: handle.to_dyn(),
109            print: if let Some(buffer) = handle.try_downcast::<Buffer>() {
110                let handle = handle.clone();
111
112                Arc::new(move |pa, orig_handle| {
113                    Buffer::update(pa, &buffer);
114
115                    handle.area.print(
116                        pa,
117                        handle.text(pa),
118                        handle.opts(pa),
119                        form::painter_with_widget::<W>(),
120                    );
121
122                    hook::trigger(pa, BufferPrinted(buffer.clone()));
123                    orig_handle.declare_as_read();
124                    orig_handle.area().0.declare_as_read();
125                })
126            } else {
127                let handle = handle.clone();
128                Arc::new(move |pa, _| {
129                    handle.area.print(
130                        pa,
131                        handle.text(pa),
132                        handle.opts(pa),
133                        form::painter_with_widget::<W>(),
134                    );
135                })
136            },
137            on_focus: Arc::new({
138                let handle = handle.clone();
139                move |pa, old| _ = hook::trigger(pa, FocusedOn((old, handle.clone())))
140            }),
141            on_unfocus: Arc::new({
142                let handle = handle.clone();
143                move |pa, new| _ = hook::trigger(pa, UnfocusedFrom((handle.clone(), new)))
144            }),
145            on_mouse_event: Arc::new({
146                let handle = handle.clone();
147                let dyn_handle = handle.to_dyn();
148                move |pa, event| {
149                    let opts = handle.opts(pa);
150                    let text = handle.text(pa);
151
152                    let points = handle.area().points_at_coord(pa, text, event.coord, opts);
153
154                    hook::trigger(pa, OnMouseEvent {
155                        handle: dyn_handle.clone(),
156                        points,
157                        coord: event.coord,
158                        kind: event.kind,
159                        modifiers: event.modifiers,
160                    });
161                    hook::trigger(pa, OnMouseEvent {
162                        handle: handle.clone(),
163                        points,
164                        coord: event.coord,
165                        kind: event.kind,
166                        modifiers: event.modifiers,
167                    });
168
169                    if let Some(TwoPointsPlace::Within(points)) = points {
170                        let event = ToggleEvent {
171                            handle: &dyn_handle,
172                            points,
173                            coord: event.coord,
174                            kind: event.kind,
175                            modifiers: event.modifiers,
176                        };
177                        let toggles = handle.text(pa).toggles_surrounding(points.real);
178
179                        crate::utils::catch_panic(|| {
180                            for (range, toggle_fn) in toggles {
181                                toggle_fn.lock().unwrap()(pa, event, range);
182                            }
183                        });
184                    }
185                }
186            }),
187        }
188    }
189
190    ////////// Reading and parts acquisition
191
192    pub(crate) fn read_as<'a, W: Widget>(&'a self, pa: &'a Pass) -> Option<&'a W> {
193        self.handle.read_as(pa)
194    }
195
196    /// The [`Widget`] of this [`Node`]
197    pub(crate) fn widget(&self) -> &RwData<dyn Widget> {
198        self.handle.widget()
199    }
200
201    /// The [`Ui::Area`] of this [`Widget`]
202    pub(crate) fn area(&self) -> &RwArea {
203        self.handle.area()
204    }
205
206    /// Returns the downcast ref of this [`Widget`].
207    pub(crate) fn try_downcast<W: Widget>(&self) -> Option<Handle<W>> {
208        self.handle.try_downcast()
209    }
210
211    /// The "parts" of this [`Node`]
212    pub(crate) fn handle(&self) -> &Handle<dyn Widget> {
213        &self.handle
214    }
215
216    ////////// Querying functions
217
218    /// Wether the value within is `W`
219    pub(crate) fn data_is<W: 'static>(&self) -> bool {
220        self.handle.widget().is::<W>()
221    }
222
223    /// Wether this and [`RwData`] point to the same value
224    pub(crate) fn ptr_eq<W: ?Sized>(&self, other: &RwData<W>) -> bool {
225        self.handle.ptr_eq(other)
226    }
227
228    /// Wether this [`Widget`] needs to be updated
229    pub(crate) fn needs_update(&self, pa: &Pass) -> bool {
230        self.handle.update_requested.load(Ordering::Relaxed)
231            || self.handle.widget().has_changed()
232            || self.handle.area.has_changed(pa)
233    }
234
235    ////////// Eventful functions
236
237    /// Updates and prints this [`Node`]
238    pub(crate) fn print(&self, pa: &mut Pass, win: usize) {
239        self.handle.update_requested.store(false, Ordering::Relaxed);
240
241        crate::context::windows().cleanup_despawned(pa);
242        if self.handle().is_closed() {
243            return;
244        }
245
246        let print_info = self.handle.area().get_print_info(pa);
247        let (widget, _) = self.handle.write_with_area(pa);
248
249        if print_info != PrintInfo::default() {
250            widget.text_mut().update_bounds();
251        }
252
253        let widgets_to_spawn = widget.text_mut().get_widget_spawns();
254        for (_, spawn) in widgets_to_spawn {
255            spawn(pa, win, self.handle.clone());
256        }
257
258        (self.print)(pa, &self.handle);
259    }
260
261    /// What to do when focusing
262    pub(crate) fn on_focus(&self, pa: &mut Pass, old: Handle<dyn Widget>) {
263        self.handle.area().set_as_active(pa);
264        (self.on_focus)(pa, old)
265    }
266
267    /// What to do when unfocusing
268    pub(crate) fn on_unfocus(&self, pa: &mut Pass, new: Handle<dyn Widget>) {
269        (self.on_unfocus)(pa, new)
270    }
271
272    pub(crate) fn on_mouse_event(&self, pa: &mut Pass, mouse_event: UiMouseEvent) {
273        (self.on_mouse_event)(pa, mouse_event);
274    }
275}
276
277impl std::fmt::Debug for Node {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        f.debug_struct("Node")
280            .field("handle", &self.handle)
281            .finish_non_exhaustive()
282    }
283}