duat_core/ui/
traits.rs

1//! Traits that should be implemented by interface implementations of
2//! Duat
3//!
4//! This module contains the traits [`RawUi`] and [`RawArea`], which
5//! should be used by RawUi implementations of Duat. By implementing
6//! those traits, you will comply with the requirements to run Duat
7//! with a custom interface, such as web app or some other type of
8//! GUI.
9//!
10//! Normally, in user code, they will encounter the
11//! [`Area`] and (sometimes) the [`Ui`] from the `type_erased` module.
12//! These are dynamic containers for the traits in this module, and
13//! are used in order to improve ergonomics and compile times.
14//!
15//! [`Area`]: super::Area
16//! [`Ui`]: super::Ui
17use bincode::{Decode, Encode};
18
19use crate::{
20    form::Painter,
21    opts::PrintOpts,
22    session::DuatSender,
23    text::{Item, SpawnId, Text, TwoPoints},
24    ui::{Caret, Coord, DynSpawnSpecs, PushSpecs},
25};
26
27/// All the methods that a working gui/tui will need to implement in
28/// order to be used by Duat.
29///
30/// This includes the following functionalities:
31///
32/// - Creating new windows, which start out with one [`RawArea`].
33/// - Spawning floating `RawArea`s around other `RawArea`s.
34/// - Spawning floating `RawArea`s in [`Text`]s, which should be able
35///   to move as the `Text` itself does.
36/// - Pushing `RawArea`s around other `RawArea`s, which include
37///   floating ones.
38/// - Closing `RawArea`s at will, which should cascading all pushed or
39///   spawned `RawArea`s
40///
41/// # Two address spaces
42///
43/// With the `RawUi` and [`RawArea`] traits, there is a dystinction
44/// that can be made between two address spaces. Since the `RawUi` is
45/// the only thing that gets initialized in the Duat runner, rather
46/// than the configuration crate, it uses the address space of Duat,
47/// not the configuration, like every other thing in Duat uses.
48///
49/// There are two main consequences to this:
50///
51/// - `&'static'` references will not match (!).
52/// - [`TypeId`]s will not match.
53///
54/// Which address space is in use is easy to tell however. If calling
55/// a function from the `RawUi` or [`RawArea`] traits, then the
56/// address space of Duat will be used. If calling any other function,
57/// _not inherent_ to these traits, then the address space of the
58/// configuration crate will be used.
59///
60/// [`TypeId`]: std::any::TypeId
61pub trait RawUi: Sized + Send + Sync + 'static {
62    /// The [`RawArea`] of this [`RawUi`]
63    type Area: RawArea;
64
65    /// Return [`Some`] only on the first call
66    fn get_once() -> Option<&'static Self>;
67
68    /// Functions to trigger when the program begins
69    fn open(&'static self, duat_tx: DuatSender);
70
71    /// Functions to trigger when the program ends
72    fn close(&'static self);
73
74    /// Initiates and returns a new "master" [`RawArea`]
75    ///
76    /// This [`RawArea`] must not have any parents, and must be placed
77    /// on a new window, that is, a plain region with nothing in
78    /// it.
79    ///
80    /// [`RawArea`]: RawUi::Area
81    fn new_root(&'static self, cache: <Self::Area as RawArea>::Cache) -> Self::Area;
82
83    /// Initiates and returns a new "floating" [`RawArea`]
84    ///
85    /// This is one of two ways of spawning floating [`Widget`]s. The
86    /// other way is with [`RawArea::spawn`], in which a `Widget`
87    /// will be bolted on the edges of another.
88    ///
89    /// TODO: There will probably be some way of defining floating
90    /// `Widget`s with coordinates in the not too distant future as
91    /// well.
92    ///
93    /// [`RawArea`]: RawUi::Area
94    /// [`Widget`]: super::Widget
95    fn new_spawned(
96        &'static self,
97        id: SpawnId,
98        specs: DynSpawnSpecs,
99        cache: <Self::Area as RawArea>::Cache,
100        win: usize,
101    ) -> Self::Area;
102
103    /// Switches the currently active window
104    ///
105    /// This will only happen to with window indices that are actual
106    /// windows. If at some point, a window index comes up that is not
107    /// actually a window, that's a bug.
108    fn switch_window(&'static self, win: usize);
109
110    /// Flush the layout
111    ///
112    /// When this function is called, it means that Duat has finished
113    /// adding or removing widgets, so the ui should calculate the
114    /// layout.
115    fn flush_layout(&'static self);
116
117    /// Prints the layout
118    ///
119    /// Since printing runs all on the same thread, it is most
120    /// efficient to call a printing function after all the widgets
121    /// are done updating, I think.
122    fn print(&'static self);
123
124    /// Functions to trigger when the program reloads
125    ///
126    /// These will happen inside of the dynamically loaded config
127    /// crate.
128    fn load(&'static self);
129
130    /// Unloads the [`RawUi`]
131    ///
132    /// Unlike [`RawUi::close`], this will happen both when Duat
133    /// reloads the configuratio and when it closes the app.
134    ///
135    /// These will happen inside of the dynamically loaded config
136    /// crate.
137    fn unload(&'static self);
138
139    /// Removes a window from the [`RawUi`]
140    ///
141    /// This should keep the current active window consistent. That
142    /// is, if the current window was ahead of the deleted one, it
143    /// should be shifted back, so that the same window is still
144    /// displayed.
145    fn remove_window(&'static self, win: usize);
146
147    /// The bottom right [`Coord`] on the screen
148    ///
149    /// Since the top left coord is `Coord { x: 0.0, y: 0.0 }`, this
150    /// is also the size of the window.
151    fn size(&'static self) -> Coord;
152}
153
154/// A region on screen where you can print [`Text`]
155///
156/// These represent the entire GUI of Duat, the only parts of the
157/// screen where text may be printed.
158///
159/// # Two address spaces
160///
161/// With the `RawUi` and `RawArea` traits, there is a dystinction
162/// that can be made between two address spaces. Since the `RawUi` is
163/// the only thing that gets initialized in the Duat runner, rather
164/// than the configuration crate, it uses the address space of Duat,
165/// not the configuration, like every other thing in Duat uses.
166///
167/// There are two main consequences to this:
168///
169/// - `&'static'` references will not match (!).
170/// - [`TypeId`]s will not match.
171///
172/// Which address space is in use is easy to tell however. If calling
173/// a function from the [`RawUi`] or `RawArea` traits, then the
174/// address space of Duat will be used. If calling any other function,
175/// _not inherent_ to these traits, then the address space of the
176/// configuration crate will be used.
177///
178/// [`TypeId`]: std::any::TypeId
179pub trait RawArea: Sized + PartialEq + 'static {
180    /// Something to be kept between app instances/reloads
181    ///
182    /// The most useful thing to keep in this case is the
183    /// [`PrintInfo`], but you could include other things
184    ///
185    /// [`PrintInfo`]: RawArea::PrintInfo
186    type Cache: Default + std::fmt::Debug + Encode + Decode<()> + 'static;
187    /// Information about what parts of a [`Text`] should be printed
188    ///
189    /// For the [`term-ui`], for example, this is quite simple, it
190    /// only needs to include the [`TwoPoints`]s on the top left
191    /// corner in order to print correctly, but your own [`RawUi`]
192    /// could differ in what it needs to keep, if it makes
193    /// use of smooth scrolling, for example.
194    ///
195    /// [`term-ui`]: docs.rs/term-ui/latest/term_ui
196    type PrintInfo: Default + Clone + Send + Sync + PartialEq + Eq + 'static;
197
198    ////////// RawArea modification
199
200    /// Creates an `RawArea` around this one
201    ///
202    /// Will return the newly created `RawArea` as well as a parent
203    /// `RawArea`, if one was created to house both of them.
204    ///
205    /// If this `RawArea` was previously [deleted], will return
206    /// [`None`].
207    ///
208    /// As an example, assuming that [`self`] has an index of `0`,
209    /// pushing an area to `self` on [`Side::Left`] would create
210    /// 2 new areas:
211    ///
212    /// ```text
213    /// ╭────────0────────╮     ╭────────1────────╮
214    /// │                 │     │╭──2───╮╭───0───╮│
215    /// │      self       │ --> ││      ││ self  ││
216    /// │                 │     │╰──────╯╰───────╯│
217    /// ╰─────────────────╯     ╰─────────────────╯
218    /// ```
219    ///
220    /// So now, there is a new area `1`, which is the parent of the
221    /// areas `0` and `2`. When a new parent is created, it should be
222    /// returned as the second element in the tuple.
223    ///
224    /// That doesn't always happen though. For example, pushing
225    /// another area to the [`Side::Right`] of `1`, `2`, or `0`,
226    /// in this situation, should not result in the creation of a
227    /// new parent:
228    ///
229    /// ```text
230    /// ╭────────1────────╮     ╭────────1────────╮
231    /// │╭──2───╮╭───0───╮│     │╭─2─╮╭──0──╮╭─3─╮│
232    /// ││      ││ self  ││     ││   ││self ││   ││
233    /// │╰──────╯╰───────╯│     │╰───╯╰─────╯╰───╯│
234    /// ╰─────────────────╯     ╰─────────────────╯
235    /// ```
236    ///
237    /// And so this function should return `(3, None)`.
238    ///
239    /// [deleted]: RawArea::delete
240    /// [`Side::Left`]: super::Side::Left
241    /// [`Side::Right`]: super::Side::Right
242    fn push(
243        &self, _: CoreAccess,
244        specs: PushSpecs,
245        on_files: bool,
246        cache: Self::Cache,
247    ) -> Option<(Self, Option<Self>)>;
248
249    /// Spawns a floating area on this `RawArea`
250    ///
251    /// This function will take a list of [`DynSpawnSpecs`], taking
252    /// the first one that fits, and readapting as the constraints
253    /// are altered
254    ///
255    /// If this `RawArea` was previously [deleted], will return
256    /// [`None`].
257    ///
258    /// [deleted]: RawArea::delete
259    fn spawn(
260        &self, _: CoreAccess,
261        id: SpawnId,
262        specs: DynSpawnSpecs,
263        cache: Self::Cache,
264    ) -> Option<Self>;
265
266    /// Deletes this `RawArea`, signaling the closing of a
267    /// [`Widget`]
268    ///
269    /// The first return value shall be `true` if the window housing
270    /// this `RawArea` should be removed.
271    ///
272    /// If the `RawArea`'s parent was also deleted, return it.
273    ///
274    /// [`Widget`]: super::Widget
275    fn delete(&self, _: CoreAccess) -> (bool, Vec<Self>);
276
277    /// Swaps this `RawArea` with another one
278    ///
279    /// The swapped `RawArea`s will be cluster masters of the
280    /// respective `RawArea`s. As such, if they belong to the same
281    /// master, nothing happens.
282    ///
283    /// This function will _never_ be called such that one of the
284    /// `RawArea`s is a decendant of the other, so the [`RawUi`]
285    /// implementor doesn't need to worry about that possibility.
286    ///
287    /// It can fail if either of the `RawArea`s was already deleted,
288    /// or if no swap happened because they belonged to the same
289    /// cluster master.
290    fn swap(&self, _: CoreAccess, rhs: &Self) -> bool;
291
292    ////////// Constraint changing functions
293
294    /// Sets a width for the `RawArea`
295    fn set_width(&self, _: CoreAccess, width: f32) -> Result<(), Text>;
296
297    /// Sets a height for the `RawArea`
298    fn set_height(&self, _: CoreAccess, height: f32) -> Result<(), Text>;
299
300    /// Hides the `RawArea`
301    fn hide(&self, _: CoreAccess) -> Result<(), Text>;
302
303    /// Reveals the `RawArea`
304    fn reveal(&self, _: CoreAccess) -> Result<(), Text>;
305
306    /// What width the given [`Text`] would occupy, if unwrapped
307    fn width_of_text(&self, _: CoreAccess, opts: PrintOpts, text: &Text) -> Result<f32, Text>;
308
309    /// Tells the [`RawUi`] that this `RawArea` is the one that is
310    /// currently focused.
311    ///
312    /// Should make `self` the active `RawArea` while deactivating
313    /// any other active `RawArea`.
314    fn set_as_active(&self, _: CoreAccess);
315
316    ////////// Printing functions
317
318    /// Prints the [`Text`]
319    fn print(&self, _: CoreAccess, text: &Text, opts: PrintOpts, painter: Painter);
320
321    /// Prints the [`Text`] with a callback function
322    fn print_with<'a>(
323        &self, _: CoreAccess,
324        text: &Text,
325        opts: PrintOpts,
326        painter: Painter,
327        f: impl FnMut(&Caret, &Item) + 'a,
328    );
329
330    /// The current printing information of the area
331    fn get_print_info(&self, _: CoreAccess) -> Self::PrintInfo;
332
333    /// Sets a previously acquired [`PrintInfo`] to the area
334    ///
335    /// [`PrintInfo`]: RawArea::PrintInfo
336    fn set_print_info(&self, _: CoreAccess, info: Self::PrintInfo);
337
338    /// Returns a printing iterator
339    ///
340    /// Given an iterator of [`text::Item`]s, returns an iterator
341    /// which assigns to each of them a [`Caret`]. This struct
342    /// essentially represents where horizontally would this character
343    /// be printed.
344    ///
345    /// If you want a reverse iterator, see
346    /// [`RawArea::rev_print_iter`].
347    ///
348    /// [`text::Item`]: Item
349    fn print_iter<'a>(
350        &self, _: CoreAccess,
351        text: &'a Text,
352        points: TwoPoints,
353        opts: PrintOpts,
354    ) -> impl Iterator<Item = (Caret, Item)> + 'a;
355
356    /// Returns a reversed printing iterator
357    ///
358    /// Given an iterator of [`text::Item`]s, returns a reversed
359    /// iterator which assigns to each of them a [`Caret`]. This
360    /// struct essentially represents where horizontally each
361    /// character would be printed.
362    ///
363    /// If you want a forwards iterator, see [`RawArea::print_iter`].
364    ///
365    /// [`text::Item`]: Item
366    fn rev_print_iter<'a>(
367        &self, _: CoreAccess,
368        text: &'a Text,
369        points: TwoPoints,
370        opts: PrintOpts,
371    ) -> impl Iterator<Item = (Caret, Item)> + 'a;
372
373    ////////// Points functions
374
375    /// Scrolls the [`Text`] veritcally by an amount
376    ///
377    /// If `scroll_beyond` is set, then the [`Text`] will be allowed
378    /// to scroll beyond the last line, up until reaching the
379    /// `scrolloff.y` value.
380    fn scroll_ver(&self, _: CoreAccess, text: &Text, dist: i32, opts: PrintOpts);
381
382    /// Scrolls the [`Text`] on all four directions until the given
383    /// [`TwoPoints`] is within the [`ScrollOff`] range
384    ///
385    /// There are two other scrolling methods for `RawArea`:
386    /// [`scroll_ver`] and [`scroll_to_points`]. The difference
387    /// between this and [`scroll_to_points`] is that this method
388    /// doesn't do anything if the [`TwoPoints`] is already on screen.
389    ///
390    /// [`ScrollOff`]: crate::opts::ScrollOff
391    /// [`scroll_ver`]: RawArea::scroll_ver
392    /// [`scroll_to_points`]: RawArea::scroll_to_points
393    fn scroll_around_points(
394        &self, _: CoreAccess,
395        text: &Text,
396        points: TwoPoints,
397        opts: PrintOpts,
398    );
399
400    /// Scrolls the [`Text`] to the visual line of a [`TwoPoints`]
401    ///
402    /// This method takes [line wrapping] into account, so it's not
403    /// the same as setting the starting points to the
404    /// [`Text::visual_line_start`] of these [`TwoPoints`].
405    ///
406    /// If `scroll_beyond` is set, then the [`Text`] will be allowed
407    /// to scroll beyond the last line, up until reaching the
408    /// `scrolloff.y` value.
409    ///
410    /// [line wrapping]: crate::opts::PrintOpts::wrap_lines
411    fn scroll_to_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts);
412
413    /// The start points that should be printed
414    fn start_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints;
415
416    /// The [`TwoPoints`] immediately after the last printed one
417    fn end_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints;
418
419    ////////// Queries
420
421    /// Whether or not [`self`] has changed
422    ///
423    /// This would mean anything relevant that wouldn't be determined
424    /// by [`PrintInfo`], this is most likely going to be the bounding
425    /// box, but it may be something else.
426    ///
427    /// [`PrintInfo`]: RawArea::PrintInfo
428    fn has_changed(&self, _: CoreAccess) -> bool;
429
430    /// Whether or not [`self`] is the "master" of `other`
431    ///
432    /// This can only happen if, by following [`self`]'s children, you
433    /// would eventually reach `other`.
434    fn is_master_of(&self, _: CoreAccess, other: &Self) -> bool;
435
436    /// Returns the clustered master of [`self`], if there is one
437    ///
438    /// If [`self`] belongs to a clustered group, return the most
439    /// senior member of said cluster, which must hold all other
440    /// members of the cluster.
441    fn get_cluster_master(&self, _: CoreAccess) -> Option<Self>;
442
443    /// Returns the statics from `self`
444    fn cache(&self, _: CoreAccess) -> Option<Self::Cache>;
445
446    /// The top left [`Coord`] of this `Area`
447    fn top_left(&self, _: CoreAccess) -> Coord;
448
449    /// The bottom right [`Coord`] of this `Area`
450    fn bottom_right(&self, _: CoreAccess) -> Coord;
451
452    /// Returns `true` if this is the currently active `RawArea`
453    ///
454    /// Only one `RawArea` should be active at any given moment.
455    fn is_active(&self, _: CoreAccess) -> bool;
456}
457
458/// A smart pointer, meant to prevent direct calling of [`RawArea`]
459/// methods
460///
461/// The methods of `RawArea` are all meant to be accessed only
462/// through the type erased `RwArea`
463#[non_exhaustive]
464#[derive(Clone, Copy)]
465pub struct CoreAccess {}
466
467impl CoreAccess {
468    pub(super) fn new() -> Self {
469        CoreAccess {}
470    }
471}