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}