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}