duat_core/ui/
mod.rs

1//! [`Ui`] structs and functions
2//!
3//! Although there is only a terminal [`Ui`] implemented at the
4//! moment, Duat is supposed to be Ui agnostic, and I plan to create a
5//! GUI app (probably in `iced` or something), and a web app as well,
6//! which is honestly more of an excuse for me to become more well
7//! versed on javascript.
8//!
9//! Each [`Ui`] is essentially a screen separated by a bunch of
10//! [`Ui::Area`]s. This happens by splitting a main [`Ui::Area`]
11//! continuously, by pushing [`Widget`]s on other [`Widget`]s. When a
12//! [`Widget`] is pushed to another, the area of the prior [`Widget`]
13//! is split in half, with [`Constraint`]s set by the [`PushSpecs`],
14//! letting the user define the exact space that each [`Widget`] will
15//! take up on the screen.
16//!
17//! Duat also supports multiple tabs, each of which is defined by a
18//! main [`Ui::Area`] that was split many times over.
19//!
20//! The [`Ui`] also supports the concept of "clustering", that is,
21//! when you push a [`Widget`] to a [`File`] via the [`OnFileOpen`]
22//! [`hook`], it gets "clustered" to that [`File`]. This means a few
23//! things. For one, if you close a [`File`], all of its clustered
24//! [`Widget`]s will also close. If you swap two [`File`]s, what you
25//! will actually swap is the [`Ui::Area`] that contains the [`File`]
26//! and all of its clustered [`Widget`].
27//!
28//! Additionally, on the terminal [`Ui`], clustering is used to
29//! determine where to draw borders between [`Ui::Area`]s, and it
30//! should be used like that in other [`Ui`] implementations as well.
31//!
32//! [`OnFileOpen`]: crate::hook::OnFileOpen
33//! [`hook`]: crate::hook
34use std::{cell::RefCell, fmt::Debug, rc::Rc, sync::mpsc, time::Instant};
35
36use bincode::{Decode, Encode};
37use crossterm::event::KeyEvent;
38use layout::window_files;
39
40use self::layout::{FileId, Layout};
41pub(crate) use self::widget::{Node, Related};
42pub use self::{
43    builder::{FileBuilder, WindowBuilder, UiBuilder, BuilderDummy, WidgetAlias},
44    widget::{Widget, WidgetCfg},
45};
46use crate::{
47    cfg::PrintCfg,
48    context::{Handle, load_cache},
49    data::{Pass, RwData},
50    file::File,
51    form::Painter,
52    text::{FwdIter, Item, Point, RevIter, Text, TwoPoints},
53};
54
55pub(crate) mod builder;
56pub mod layout;
57mod widget;
58
59/// All the methods that a working gui/tui will need to implement, in
60/// order to use Duat.
61///
62/// # NOTE
63///
64/// The dependencies on [`Clone`] and [`Default`] is only here for
65/// convenience. Many types require a [`Ui`] as a generic parameter,
66/// and if [`Ui`] does not implement [`Clone`] or [`Default`],
67/// deriving [`Clone`] or [`Default`] for said types would
68/// be a very manual task.
69///
70/// Below is the recommended implementation of [`Clone`] adn
71/// [`Default`] for all types that implement [`Ui`]:
72///
73/// ```rust
74/// # mod duat_smart_fridge {
75/// #     pub struct Ui;
76/// # }
77/// impl Clone for duat_smart_fridge::Ui {
78///     fn clone(&self) -> Self {
79///         panic!("You are not supposed to clone the Ui");
80///     }
81/// }
82/// impl Default for duat_smart_fridge::Ui {
83///     fn default() -> Self {
84///         panic!("You are not supposed to call the Ui's default constructor");
85///     }
86/// }
87/// ```
88pub trait Ui: Default + Clone + 'static {
89    /// The [`RawArea`] of this [`Ui`]
90    type Area: RawArea<Ui = Self>;
91    /// Variables to initialize at the Duat application, outside the
92    /// config
93    ///
94    /// Of the ways that Duat can be extended and modified, only the
95    /// [`Ui`] can be accessed by the Duat executor itself, since it
96    /// is one of its dependencies. This means that it is possible to
97    /// keep some things between reloads.
98    ///
99    /// This is particularly useful for some kinds of static
100    /// variables. For example, in [`term-ui`], it makes heavy use of
101    /// [`std`] defined functions to print to the terminal. Those use
102    /// a static [`Mutex`] internally, and I have found that it is
103    /// better to use the one from the Duat app, rather than one from
104    /// the config crate
105    ///
106    /// [`term-ui`]: docs.rs/term-ui/latest/term_ui
107    /// [`Mutex`]: std::sync::Mutex
108    type MetaStatics: Default;
109
110    ////////// Functions executed from the outer loop
111
112    /// Functions to trigger when the program begins
113    ///
114    /// These will happen in the main `duat` runner
115    fn open(ms: &'static Self::MetaStatics, duat_tx: Sender);
116
117    /// Functions to trigger when the program ends
118    ///
119    /// These will happen in the main `duat` runner
120    fn close(ms: &'static Self::MetaStatics);
121
122    ////////// Functions executed from within the configuratio loop
123
124    /// Initiates and returns a new "master" [`Area`]
125    ///
126    /// This [`Area`] must not have any parents, and must be placed on
127    /// a new window, that is, a plain region with nothing in it.
128    ///
129    /// [`Area`]: Ui::Area
130    fn new_root(
131        ms: &'static Self::MetaStatics,
132        cache: <Self::Area as RawArea>::Cache,
133    ) -> Self::Area;
134
135    /// Switches the currently active window
136    ///
137    /// This will only happen to with window indices that are actual
138    /// windows. If at some point, a window index comes up that is not
139    /// actually a window, that's a bug.
140    fn switch_window(ms: &'static Self::MetaStatics, win: usize);
141
142    /// Flush the layout
143    ///
144    /// When this function is called, it means that Duat has finished
145    /// adding or removing widgets, so the ui should calculate the
146    /// layout.
147    fn flush_layout(ms: &'static Self::MetaStatics);
148
149    /// Functions to trigger when the program reloads
150    ///
151    /// These will happen inside of the dynamically loaded config
152    /// crate.
153    fn load(ms: &'static Self::MetaStatics);
154
155    /// Unloads the [`Ui`]
156    ///
157    /// Unlike [`Ui::close`], this will happen both when Duat reloads
158    /// the configuratio and when it closes the app.
159    ///
160    /// These will happen inside of the dynamically loaded config
161    /// crate.
162    fn unload(ms: &'static Self::MetaStatics);
163
164    /// Removes a window from the [`Ui`]
165    ///
166    /// This should keep the current active window consistent. That
167    /// is, if the current window was ahead of the deleted one, it
168    /// should be shifted back, so that the same window is still
169    /// displayed.
170    fn remove_window(ms: &'static Self::MetaStatics, win: usize);
171}
172
173/// An [`RawArea`] that supports printing [`Text`]
174///
175/// These represent the entire GUI of Duat, the only parts of the
176/// screen where text may be printed.
177pub trait RawArea: Clone + PartialEq + Sized + 'static {
178    /// The [`Ui`] this [`RawArea`] belongs to
179    type Ui: Ui<Area = Self>;
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 + 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 real and ghost [`Point`]s on the
191    /// top left corner in order to print correctly, but your own
192    /// [`Ui`] 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 + PartialEq + Eq;
197
198    ////////// Area modification
199
200    /// Bisects the [`RawArea`][Ui::Area] with the given index into
201    /// two.
202    ///
203    /// Will return 2 indices, the first one is the index of a new
204    /// area. The second is an index for a newly created parent
205    ///
206    /// As an example, assuming that [`self`] has an index of `0`,
207    /// pushing an area to [`self`] on [`Side::Left`] would create
208    /// 2 new areas:
209    ///
210    /// ```text
211    /// ╭────────0────────╮     ╭────────1────────╮
212    /// │                 │     │╭──2───╮╭───0───╮│
213    /// │      self       │ --> ││      ││ self  ││
214    /// │                 │     │╰──────╯╰───────╯│
215    /// ╰─────────────────╯     ╰─────────────────╯
216    /// ```
217    ///
218    /// So now, there is a new area `1`, which is the parent of the
219    /// areas `0` and `2`. When a new parent is created, it should be
220    /// returned as the second element in the tuple.
221    ///
222    /// That doesn't always happen though. For example, pushing
223    /// another area to the [`Side::Right`] of `1`, `2`, or `0`,
224    /// in this situation, should not result in the creation of a
225    /// new parent:
226    ///
227    /// ```text
228    /// ╭────────1────────╮     ╭────────1────────╮
229    /// │╭──2───╮╭───0───╮│     │╭─2─╮╭──0──╮╭─3─╮│
230    /// ││      ││ self  ││     ││   ││self ││   ││
231    /// │╰──────╯╰───────╯│     │╰───╯╰─────╯╰───╯│
232    /// ╰─────────────────╯     ╰─────────────────╯
233    /// ```
234    ///
235    /// And so [`RawArea::bisect`] should return `(3, None)`.
236    fn bisect(
237        area: MutArea<Self>,
238        specs: PushSpecs,
239        cluster: bool,
240        on_files: bool,
241        cache: Self::Cache,
242    ) -> (Self, Option<Self>);
243
244    /// Deletes this [`RawArea`], signaling the closing of a
245    /// [`Widget`]
246    ///
247    /// If the [`RawArea`]'s parent was also deleted, return it.
248    fn delete(area: MutArea<Self>) -> Option<Self>;
249
250    /// Swaps this [`RawArea`] with another one
251    ///
252    /// The swapped [`RawArea`]s will be cluster masters of the
253    /// respective [`RawArea`]s. As such, if they belong to the same
254    /// master, nothing happens.
255    fn swap(lhs: MutArea<Self>, rhs: &Self);
256
257    /// Spawns a floating area on this [`RawArea`]
258    ///
259    /// This function will take a list of [`SpawnSpecs`], taking the
260    /// first one that fits, and readapting as the constraints are
261    /// altered
262    fn spawn_floating(area: MutArea<Self>, specs: SpawnSpecs) -> Result<Self, Text>;
263
264    /// Spawns a floating area
265    ///
266    /// If the [`TwoPoints`] parameter is not specified, this new
267    /// [`RawArea`] will be pushed to the edges of the old one, the
268    /// pushed edge being the same as the [`Side`] used for pushing.
269    ///
270    /// If there is a [`TwoPoints`] argument, the
271    fn spawn_floating_at(
272        area: MutArea<Self>,
273        specs: SpawnSpecs,
274        at: impl TwoPoints,
275        text: &Text,
276        cfg: PrintCfg,
277    ) -> Result<Self, Text>;
278
279    ////////// Constraint changing functions
280
281    /// Changes the horizontal constraint of the area
282    fn constrain_hor(&self, cons: impl IntoIterator<Item = Constraint>) -> Result<(), Text>;
283
284    /// Changes the vertical constraint of the area
285    fn constrain_ver(&self, cons: impl IntoIterator<Item = Constraint>) -> Result<(), Text>;
286
287    /// Changes [`Constraint`]s such that the [`RawArea`] becomes
288    /// hidden
289    fn hide(&self) -> Result<(), Text>;
290
291    /// Changes [`Constraint`]s such that the [`RawArea`] is revealed
292    fn reveal(&self) -> Result<(), Text>;
293
294    /// Requests that the width be enough to fit a certain piece of
295    /// text.
296    fn request_width_to_fit(&self, text: &str) -> Result<(), Text>;
297
298    /// Scrolls the [`Text`] veritcally by an amount
299    ///
300    /// If `scroll_beyond` is set, then the [`Text`] will be allowed
301    /// to scroll beyond the last line, up until reaching the
302    /// `scrolloff.y` value.
303    fn scroll_ver(&self, text: &Text, dist: i32, cfg: PrintCfg);
304
305    /// Scrolls the [`Text`] on all four directions until the given
306    /// [`Point`] is within the [`ScrollOff`] range
307    ///
308    /// There are two other scrolling methods for [`RawArea`]:
309    /// [`scroll_ver`] and [`scroll_to_points`]. The difference
310    /// between this and [`scroll_to_points`] is that this method
311    /// doesn't do anything if the [`Point`] is already on screen.
312    ///
313    /// [`ScrollOff`]: crate::cfg::ScrollOff
314    /// [`scroll_ver`]: RawArea::scroll_ver
315    /// [`scroll_to_points`]: RawArea::scroll_to_points
316    fn scroll_around_point(&self, text: &Text, point: Point, cfg: PrintCfg);
317
318    /// Scrolls the [`Text`] to the visual line of a [`TwoPoints`]
319    ///
320    /// This method takes [line wrapping] into account, so it's not
321    /// the same as setting the starting points to the
322    /// [`Text::visual_line_start`] of these [`TwoPoints`].
323    ///
324    /// If `scroll_beyond` is set, then the [`Text`] will be allowed
325    /// to scroll beyond the last line, up until reaching the
326    /// `scrolloff.y` value.
327    ///
328    /// [line wrapping]: crate::cfg::WrapMethod
329    fn scroll_to_points(&self, text: &Text, points: impl TwoPoints, cfg: PrintCfg);
330
331    /// Tells the [`Ui`] that this [`RawArea`] is the one that is
332    /// currently focused.
333    ///
334    /// Should make [`self`] the active [`RawArea`] while deactivating
335    /// any other active [`RawArea`].
336    fn set_as_active(&self);
337
338    ////////// Printing
339
340    /// Prints the [`Text`] via an [`Iterator`]
341    fn print(&self, text: &mut Text, cfg: PrintCfg, painter: Painter);
342
343    /// Prints the [`Text`] with a callback function
344    fn print_with<'a>(
345        &self,
346        text: &mut Text,
347        cfg: PrintCfg,
348        painter: Painter,
349        f: impl FnMut(&Caret, &Item) + 'a,
350    );
351
352    /// Sets a previously acquired [`PrintInfo`] to the area
353    ///
354    /// [`PrintInfo`]: RawArea::PrintInfo
355    fn set_print_info(&self, info: Self::PrintInfo);
356
357    /// Returns a printing iterator
358    ///
359    /// Given an iterator of [`text::Item`]s, returns an iterator
360    /// which assigns to each of them a [`Caret`]. This struct
361    /// essentially represents where horizontally would this character
362    /// be printed.
363    ///
364    /// If you want a reverse iterator, see
365    /// [`RawArea::rev_print_iter`].
366    ///
367    /// [`text::Item`]: Item
368    fn print_iter<'a>(
369        &self,
370        iter: FwdIter<'a>,
371        cfg: PrintCfg,
372    ) -> impl Iterator<Item = (Caret, Item)> + Clone + 'a;
373
374    /// Returns a reversed printing iterator
375    ///
376    /// Given an iterator of [`text::Item`]s, returns a reversed
377    /// iterator which assigns to each of them a [`Caret`]. This
378    /// struct essentially represents where horizontally each
379    /// character would be printed.
380    ///
381    /// If you want a forwards iterator, see [`RawArea::print_iter`].
382    ///
383    /// [`text::Item`]: Item
384    fn rev_print_iter<'a>(
385        &self,
386        iter: RevIter<'a>,
387        cfg: PrintCfg,
388    ) -> impl Iterator<Item = (Caret, Item)> + Clone + 'a;
389
390    ////////// Queries
391
392    /// Whether or not [`self`] has changed
393    ///
394    /// This would mean anything relevant that wouldn't be determined
395    /// by [`PrintInfo`], this is most likely going to be the bounding
396    /// box, but it may be something else.
397    ///
398    /// [`PrintInfo`]: RawArea::PrintInfo
399    fn has_changed(&self) -> bool;
400
401    /// Whether or not [`self`] is the "master" of `other`
402    ///
403    /// This can only happen if, by following [`self`]'s children, you
404    /// would eventually reach `other`.
405    fn is_master_of(&self, other: &Self) -> bool;
406
407    /// Returns the clustered master of [`self`], if there is one
408    ///
409    /// If [`self`] belongs to a clustered group, return the most
410    /// senior member of said cluster, which must hold all other
411    /// members of the cluster.
412    fn get_cluster_master(&self) -> Option<Self>;
413
414    /// Returns the statics from `self`
415    fn cache(&self) -> Option<Self::Cache>;
416
417    /// Gets the width of the area
418    fn width(&self) -> u32;
419
420    /// Gets the height of the area
421    fn height(&self) -> u32;
422
423    /// The start points that should be printed
424    fn start_points(&self, text: &Text, cfg: PrintCfg) -> (Point, Option<Point>);
425
426    /// The end points that should be printed
427    fn end_points(&self, text: &Text, cfg: PrintCfg) -> (Point, Option<Point>);
428
429    /// The current printing information of the area
430    fn print_info(&self) -> Self::PrintInfo;
431
432    /// Returns `true` if this is the currently active [`RawArea`]
433    ///
434    /// Only one [`RawArea`] should be active at any given moment.
435    fn is_active(&self) -> bool;
436}
437
438/// A container for a master [`RawArea`] in Duat
439#[doc(hidden)]
440pub struct Window<U: Ui> {
441    nodes: Vec<Node<U>>,
442    files_area: U::Area,
443    layout: Box<dyn Layout<U>>,
444}
445
446impl<U: Ui> Window<U> {
447    /// Returns a new instance of [`Window`]
448    pub(crate) fn new<W: Widget<U>>(
449        pa: &mut Pass,
450        ms: &'static U::MetaStatics,
451        widget: W,
452        layout: Box<dyn Layout<U>>,
453    ) -> (Self, Node<U>) {
454        let widget =
455            unsafe { RwData::<dyn Widget<U>>::new_unsized::<W>(Rc::new(RefCell::new(widget))) };
456
457        let cache = widget
458            .read_as(pa, |f: &File<U>| {
459                load_cache::<<U::Area as RawArea>::Cache>(f.path())
460            })
461            .flatten()
462            .unwrap_or_default();
463
464        let area = U::new_root(ms, cache);
465
466        let node = Node::new::<W>(widget, area.clone());
467
468        let window = Self {
469            nodes: vec![node.clone()],
470            files_area: area.clone(),
471            layout,
472        };
473
474        (window, node)
475    }
476
477    /// Returns a new [`Window`] from raw elements
478    pub(crate) fn from_raw(
479        files_area: U::Area,
480        nodes: Vec<Node<U>>,
481        layout: Box<dyn Layout<U>>,
482    ) -> Self {
483        let files_area = files_area.get_cluster_master().unwrap_or(files_area);
484        Self { nodes, files_area, layout }
485    }
486
487    /// Pushes a [`Widget`] onto an existing one
488    pub(crate) fn push<W: Widget<U>>(
489        &mut self,
490        pa: &mut Pass,
491        widget: W,
492        area: &U::Area,
493        specs: PushSpecs,
494        do_cluster: bool,
495        on_files: bool,
496    ) -> (Node<U>, Option<U::Area>) {
497        #[inline(never)]
498        fn get_areas<U: Ui>(
499            pa: &mut Pass,
500            area: &<U as Ui>::Area,
501            specs: PushSpecs,
502            do_cluster: bool,
503            on_files: bool,
504            widget: &RwData<dyn Widget<U> + 'static>,
505        ) -> (U::Area, Option<U::Area>) {
506            let cache = widget
507                .read_as(pa, |f: &File<U>| {
508                    load_cache::<<U::Area as RawArea>::Cache>(f.path())
509                })
510                .flatten()
511                .unwrap_or_default();
512
513            let (child, parent) = MutArea(area).bisect(specs, do_cluster, on_files, cache);
514
515            (child, parent)
516        }
517
518        let widget =
519            unsafe { RwData::<dyn Widget<U>>::new_unsized::<W>(Rc::new(RefCell::new(widget))) };
520
521        let (child, parent) = get_areas(pa, area, specs, do_cluster, on_files, &widget);
522
523        self.nodes.push(Node::new::<W>(widget, child));
524
525        (self.nodes.last().unwrap().clone(), parent)
526    }
527
528    /// Pushes a [`File`] to the file's parent
529    ///
530    /// This function will push to the edge of `self.files_parent`
531    /// This is an area, usually in the center, that contains all
532    /// [`File`]s, and their associated [`Widget`]s,
533    /// with others being at the perifery of this area.
534    pub(crate) fn push_file(
535        &mut self,
536        pa: &mut Pass,
537        mut file: File<U>,
538    ) -> Result<(Node<U>, Option<U::Area>), Text> {
539        let window_files = window_files(pa, &self.nodes);
540        file.layout_order = window_files.len();
541        let (id, specs) = self.layout.new_file(&file, window_files)?;
542
543        let (child, parent) = self.push(pa, file, &id.0, specs, false, true);
544
545        if let Some(parent) = &parent
546            && id.0 == self.files_area
547        {
548            self.files_area = parent.clone();
549        }
550
551        Ok((child, parent))
552    }
553
554    /// Removes all [`Node`]s whose [`RawArea`]s where deleted
555    pub(crate) fn remove_file(&mut self, pa: &Pass, name: &str) {
556        let Some(node) = self
557            .nodes
558            .extract_if(.., |node| {
559                node.as_file()
560                    .is_some_and(|(handle, ..)| handle.read(pa, |file, _| file.name()) == name)
561            })
562            .next()
563        else {
564            return;
565        };
566
567        // If this is the case, this means there is only one File left in this
568        // Window, so the files_area should be the cluster master of that
569        // File.
570        if let Some(parent) = MutArea(node.area()).delete()
571            && parent == self.files_area
572        {
573            let files = self.file_nodes(pa);
574            let (_, FileId(area)) = files.first().unwrap();
575            self.files_area = area.clone();
576        }
577
578        if let Some(related) = node.related_widgets() {
579            related.read(pa, |related| {
580                for node in self.nodes.extract_if(.., |node| related.contains(node)) {
581                    MutArea(node.area()).delete();
582                }
583            });
584        }
585    }
586
587    /// Takes all [`Node`]s related to a given [`Node`]
588    pub(crate) fn take_file_and_related_nodes(
589        &mut self,
590        pa: &mut Pass,
591        node: &Node<U>,
592    ) -> Vec<Node<U>> {
593        if let Some(related) = node.related_widgets() {
594            let lo = node
595                .widget()
596                .read_as(pa, |f: &File<U>| f.layout_order)
597                .unwrap();
598
599            let nodes = related.read(pa, |related| {
600                self.nodes
601                    .extract_if(.., |n| related.contains(n) || n == node)
602                    .collect()
603            });
604
605            for node in &self.nodes {
606                node.widget().write_as(pa, |f: &mut File<U>| {
607                    if f.layout_order > lo {
608                        f.layout_order -= 1;
609                    }
610                });
611            }
612
613            nodes
614        } else {
615            Vec::new()
616        }
617    }
618
619    /// Inserts [`File`] nodes orderly
620    pub(crate) fn insert_file_nodes(
621        &mut self,
622        pa: &mut Pass,
623        layout_ordering: usize,
624        nodes: Vec<Node<U>>,
625    ) {
626        if let Some(i) = self.nodes.iter().position(|node| {
627            node.widget()
628                .read_as(pa, |f: &File<U>| f.layout_order >= layout_ordering)
629                == Some(true)
630        }) {
631            for node in self.nodes[i..].iter() {
632                node.widget().write_as(pa, |f: &mut File<U>| {
633                    f.layout_order += 1;
634                });
635            }
636            self.nodes.splice(i..i, nodes);
637        } else {
638            self.nodes.extend(nodes);
639        }
640    }
641
642    /// An [`Iterator`] over the [`Node`]s in a [`Window`]
643    pub(crate) fn nodes(&self) -> impl ExactSizeIterator<Item = &Node<U>> + DoubleEndedIterator {
644        self.nodes.iter()
645    }
646
647    /// Returns an [`Iterator`] over the names of [`File`]s
648    /// and their respective [`Widget`] indices
649    ///
650    /// [`Widget`]: crate::ui::Widget
651    pub fn file_names(&self, pa: &Pass) -> Vec<String> {
652        window_files(pa, &self.nodes)
653            .into_iter()
654            .map(|f| f.0.widget().read_as(pa, |f: &File<U>| f.name()).unwrap())
655            .collect()
656    }
657
658    /// Returns an [`Iterator`] over the paths of [`File`]s
659    /// and their respective [`Widget`] indices
660    ///
661    /// [`Widget`]: crate::ui::Widget
662    pub fn file_paths(&self, pa: &Pass) -> Vec<String> {
663        window_files(pa, &self.nodes)
664            .into_iter()
665            .map(|f| f.0.widget().read_as(pa, |f: &File<U>| f.name()).unwrap())
666            .collect()
667    }
668
669    /// An [`Iterator`] over the [`File`] [`Node`]s in a [`Window`]
670    pub(crate) fn file_nodes(&self, pa: &Pass) -> Vec<(Handle<File<U>, U>, FileId<U>)> {
671        window_files(pa, &self.nodes)
672    }
673
674    /// How many [`Widget`]s are in this [`Window`]
675    pub(crate) fn len_widgets(&self) -> usize {
676        self.nodes.len()
677    }
678}
679
680/// A dimension on screen, can either be horizontal or vertical
681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
682pub enum Axis {
683    /// The horizontal axis
684    Horizontal,
685    /// The vertical axis
686    Vertical,
687}
688
689impl Axis {
690    /// The [`Axis`] perpendicular to this one
691    pub fn perp(&self) -> Self {
692        match self {
693            Axis::Horizontal => Axis::Vertical,
694            Axis::Vertical => Axis::Horizontal,
695        }
696    }
697
698    /// Returns `true` if the axis is [`Horizontal`].
699    ///
700    /// [`Horizontal`]: Axis::Horizontal
701    #[must_use]
702    pub fn is_hor(&self) -> bool {
703        matches!(self, Self::Horizontal)
704    }
705
706    /// Returns `true` if the axis is [`Vertical`].
707    ///
708    /// [`Vertical`]: Axis::Vertical
709    #[must_use]
710    pub fn is_ver(&self) -> bool {
711        matches!(self, Self::Vertical)
712    }
713}
714
715impl From<PushSpecs> for Axis {
716    fn from(value: PushSpecs) -> Self {
717        match value.side {
718            Side::Above | Side::Below => Axis::Vertical,
719            _ => Axis::Horizontal,
720        }
721    }
722}
723
724/// An event that Duat must handle
725#[doc(hidden)]
726pub enum DuatEvent {
727    /// A [`KeyEvent`] was typed
728    Tagger(KeyEvent),
729    /// A function was queued
730    QueuedFunction(Box<dyn FnOnce(&mut Pass) + Send>),
731    /// The application resized
732    Resize,
733    /// A [`Form`] was altered, which one it is, doesn't matter
734    ///
735    /// [`Form`]: crate::form::Form
736    FormChange,
737    /// Open a new [`File`]
738    OpenFile(String),
739    /// Close an open [`File`]
740    CloseFile(String),
741    /// Swap two [`File`]s
742    SwapFiles(String, String),
743    /// Open a new window with a [`File`]
744    OpenWindow(String),
745    /// Switch to the n'th window
746    SwitchWindow(usize),
747    /// Started a reload/recompilation at an [`Instant`]
748    ReloadStarted(Instant),
749    /// The Duat app sent a message to reload the config
750    ReloadConfig,
751    /// Quit Duat
752    Quit,
753}
754
755impl Debug for DuatEvent {
756    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
757        match self {
758            Self::Tagger(key) => f.debug_tuple("Tagger").field(key).finish(),
759            Self::Resize => write!(f, "Resize"),
760            Self::FormChange => write!(f, "FormChange"),
761            Self::ReloadConfig => write!(f, "ReloadConfig"),
762            Self::OpenFile(file) => f.debug_tuple("OpenFile").field(file).finish(),
763            Self::CloseFile(file) => f.debug_tuple("CloseFile").field(file).finish(),
764            Self::SwapFiles(lhs, rhs) => f.debug_tuple("SwapFiles").field(lhs).field(rhs).finish(),
765            Self::OpenWindow(file) => f.debug_tuple("OpenWindow").field(file).finish(),
766            Self::SwitchWindow(win) => f.debug_tuple("SwitchWindow").field(win).finish(),
767            Self::ReloadStarted(instant) => f.debug_tuple("ReloadStarted").field(instant).finish(),
768            Self::QueuedFunction(_) => f.debug_struct("InnerFunction").finish(),
769            Self::Quit => write!(f, "Quit"),
770        }
771    }
772}
773
774/// A sender of [`DuatEvent`]s
775pub struct Sender(&'static mpsc::Sender<DuatEvent>);
776
777impl Sender {
778    /// Returns a new [`Sender`]
779    pub fn new(sender: &'static mpsc::Sender<DuatEvent>) -> Self {
780        Self(sender)
781    }
782
783    /// Sends a [`KeyEvent`]
784    pub fn send_key(&self, key: KeyEvent) -> Result<(), mpsc::SendError<DuatEvent>> {
785        self.0.send(DuatEvent::Tagger(key))
786    }
787
788    /// Sends a notice that the app has resized
789    pub fn send_resize(&self) -> Result<(), mpsc::SendError<DuatEvent>> {
790        self.0.send(DuatEvent::Resize)
791    }
792
793    /// Sends a notice that a [`Form`] has changed
794    ///
795    /// [`Form`]: crate::form::Form
796    pub(crate) fn send_form_changed(&self) -> Result<(), mpsc::SendError<DuatEvent>> {
797        self.0.send(DuatEvent::FormChange)
798    }
799}
800
801/// Information on how a [`Widget`] should be pushed onto another
802///
803/// This information is composed of three parts:
804///
805/// * A side to push;
806/// * A horizontal [`Constraint`];
807/// * A vertical [`Constraint`];
808///
809/// Constraints are demands that must be met by the widget's
810/// [`RawArea`], on a best effort basis.
811///
812/// So, for example, if the [`PushSpecs`] are:
813///
814/// ```rust
815/// use duat_core::ui::PushSpecs;
816/// let specs = PushSpecs::left().with_hor_len(3.0).with_ver_ratio(2, 3);
817/// ```
818///
819/// Then the widget should be pushed to the left, with a width of 3,
820/// and its height should be equal to two thirds of the area directly
821/// below.
822#[derive(Clone, Copy, Debug)]
823pub struct PushSpecs {
824    side: Side,
825    ver_cons: [Option<Constraint>; 4],
826    hor_cons: [Option<Constraint>; 4],
827    is_hidden: bool,
828}
829
830impl PushSpecs {
831    /// Push the [`Widget`] to the left
832    pub const fn left() -> Self {
833        Self {
834            side: Side::Left,
835            ver_cons: [None; 4],
836            hor_cons: [None; 4],
837            is_hidden: false,
838        }
839    }
840
841    /// Push the [`Widget`] to the right
842    pub const fn right() -> Self {
843        Self {
844            side: Side::Right,
845            ver_cons: [None; 4],
846            hor_cons: [None; 4],
847            is_hidden: false,
848        }
849    }
850
851    /// Push the [`Widget`] above
852    pub const fn above() -> Self {
853        Self {
854            side: Side::Above,
855            ver_cons: [None; 4],
856            hor_cons: [None; 4],
857            is_hidden: false,
858        }
859    }
860
861    /// Push the [`Widget`] below
862    pub const fn below() -> Self {
863        Self {
864            side: Side::Below,
865            ver_cons: [None; 4],
866            hor_cons: [None; 4],
867            is_hidden: false,
868        }
869    }
870
871    /// Turns this [`Widget`] hidden by default
872    ///
873    /// Hiding [`Widget`]s, as opposed to calling something like
874    /// [`PushSpecs::with_hor_len(0.0)`] has a few advantages.
875    ///
876    /// - Can be undone just by calling [`RawArea::reveal`]
877    /// - Can be redone just by calling [`RawArea::hide`]
878    /// - Is agnostic to other [`Constraint`]s, i.e., kind of
879    ///   memorizes what they should be before being hidden.
880    ///
881    /// [`PushSpecs::with_hor_len(0.0)`]: PushSpecs::with_hor_len
882    pub const fn hidden(self) -> Self {
883        Self { is_hidden: true, ..self }
884    }
885
886    /// Changes the direction of pushing to the left
887    pub const fn to_left(self) -> Self {
888        Self { side: Side::Left, ..self }
889    }
890
891    /// Changes the direction of pushing to the right
892    pub const fn to_right(self) -> Self {
893        Self { side: Side::Right, ..self }
894    }
895
896    /// Changes the direction of pushing to above
897    pub const fn to_above(self) -> Self {
898        Self { side: Side::Above, ..self }
899    }
900
901    /// Changes the direction of pushing to below
902    pub const fn to_below(self) -> Self {
903        Self { side: Side::Below, ..self }
904    }
905
906    /// Sets the required vertical length
907    pub const fn with_ver_len(mut self, len: f32) -> Self {
908        constrain(&mut self.ver_cons, Constraint::Len(len));
909        self
910    }
911
912    /// Sets the minimum vertical length
913    pub const fn with_ver_min(mut self, min: f32) -> Self {
914        constrain(&mut self.ver_cons, Constraint::Min(min));
915        self
916    }
917
918    /// Sets the maximum vertical length
919    pub const fn with_ver_max(mut self, max: f32) -> Self {
920        constrain(&mut self.ver_cons, Constraint::Max(max));
921        self
922    }
923
924    /// Sets the vertical ratio between it and its parent
925    pub const fn with_ver_ratio(mut self, den: u16, div: u16) -> Self {
926        constrain(&mut self.ver_cons, Constraint::Ratio(den, div));
927        self
928    }
929
930    /// Sets the required horizontal length
931    pub const fn with_hor_len(mut self, len: f32) -> Self {
932        constrain(&mut self.hor_cons, Constraint::Len(len));
933        self
934    }
935
936    /// Sets the minimum horizontal length
937    pub const fn with_hor_min(mut self, min: f32) -> Self {
938        constrain(&mut self.hor_cons, Constraint::Min(min));
939        self
940    }
941
942    /// Sets the maximum horizontal length
943    pub const fn with_hor_max(mut self, max: f32) -> Self {
944        constrain(&mut self.hor_cons, Constraint::Max(max));
945        self
946    }
947
948    /// Sets the horizontal ratio between it and its parent
949    pub const fn with_hor_ratio(mut self, den: u16, div: u16) -> Self {
950        constrain(&mut self.hor_cons, Constraint::Ratio(den, div));
951        self
952    }
953
954    /// Wether this [`Widget`] should default to being [hidden] or not
955    ///
956    /// [hidden]: Self::hidden
957    pub const fn is_hidden(&self) -> bool {
958        self.is_hidden
959    }
960
961    /// The [`Axis`] where it will be pushed
962    ///
963    /// - left/right: [`Axis::Horizontal`]
964    /// - above/below: [`Axis::Vertical`]
965    pub const fn axis(&self) -> Axis {
966        match self.side {
967            Side::Above | Side::Below => Axis::Vertical,
968            Side::Right | Side::Left => Axis::Horizontal,
969        }
970    }
971
972    /// The [`Side`] where it will be pushed
973    pub const fn side(&self) -> Side {
974        self.side
975    }
976
977    /// Wether this "comes earlier" on the screen
978    ///
979    /// This returns true if `self.side() == Side::Left || self.side()
980    /// == Side::Above`, since that is considered "earlier" on
981    /// screens.
982    pub const fn comes_earlier(&self) -> bool {
983        matches!(self.side, Side::Left | Side::Above)
984    }
985
986    /// An [`Iterator`] over the vertical constraints
987    pub fn ver_cons(&self) -> impl Iterator<Item = Constraint> + Clone {
988        self.ver_cons.into_iter().flatten()
989    }
990
991    /// An [`Iterator`] over the horizontal constraints
992    pub fn hor_cons(&self) -> impl Iterator<Item = Constraint> + Clone {
993        self.hor_cons.into_iter().flatten()
994    }
995
996    /// The constraints on a given [`Axis`]
997    pub fn cons_on(&self, axis: Axis) -> impl Iterator<Item = Constraint> {
998        match axis {
999            Axis::Horizontal => self.hor_cons.into_iter().flatten(),
1000            Axis::Vertical => self.ver_cons.into_iter().flatten(),
1001        }
1002    }
1003
1004    /// Wether it is resizable in an [`Axis`]
1005    ///
1006    /// It will be resizable if there are no [`Constraint::Len`] in
1007    /// that [`Axis`].
1008    pub const fn is_resizable_on(&self, axis: Axis) -> bool {
1009        let cons = match axis {
1010            Axis::Horizontal => &self.hor_cons,
1011            Axis::Vertical => &self.ver_cons,
1012        };
1013
1014        let mut i = 0;
1015
1016        while i < 4 {
1017            let (None | Some(Constraint::Min(..) | Constraint::Max(..))) = cons[i] else {
1018                return false;
1019            };
1020
1021            i += 1;
1022        }
1023
1024        true
1025    }
1026}
1027
1028/// Much like [`PushSpecs`], but for floating [`Widget`]s
1029#[derive(Debug, Clone)]
1030pub struct SpawnSpecs {
1031    /// Potential spawning [`Corner`]s to connect to and from
1032    pub choices: Vec<[Corner; 2]>,
1033    ver_cons: [Option<Constraint>; 4],
1034    hor_cons: [Option<Constraint>; 4],
1035}
1036
1037impl SpawnSpecs {
1038    /// Returns a new [`SpawnSpecs`] from possible [`Corner`]s
1039    pub fn new(choices: impl IntoIterator<Item = [Corner; 2]>) -> Self {
1040        Self {
1041            choices: choices.into_iter().collect(),
1042            ver_cons: [None; 4],
1043            hor_cons: [None; 4],
1044        }
1045    }
1046
1047    /// Adds more [`Corner`]s as fallback to spawn on
1048    pub fn with_fallbacks(mut self, choices: impl IntoIterator<Item = [Corner; 2]>) -> Self {
1049        self.choices.extend(choices);
1050        self
1051    }
1052
1053    /// Sets the required vertical length
1054    pub fn with_ver_len(mut self, len: f32) -> Self {
1055        constrain(&mut self.ver_cons, Constraint::Len(len));
1056        self
1057    }
1058
1059    /// Sets the minimum vertical length
1060    pub fn with_ver_min(mut self, min: f32) -> Self {
1061        constrain(&mut self.ver_cons, Constraint::Min(min));
1062        self
1063    }
1064
1065    /// Sets the maximum vertical length
1066    pub fn with_ver_max(mut self, max: f32) -> Self {
1067        constrain(&mut self.ver_cons, Constraint::Max(max));
1068        self
1069    }
1070
1071    /// Sets the vertical ratio between it and its parent
1072    pub fn with_ver_ratio(mut self, den: u16, div: u16) -> Self {
1073        constrain(&mut self.ver_cons, Constraint::Ratio(den, div));
1074        self
1075    }
1076
1077    /// Sets the required horizontal length
1078    pub fn with_hor_len(mut self, len: f32) -> Self {
1079        constrain(&mut self.hor_cons, Constraint::Len(len));
1080        self
1081    }
1082
1083    /// Sets the minimum horizontal length
1084    pub fn with_hor_min(mut self, min: f32) -> Self {
1085        constrain(&mut self.hor_cons, Constraint::Min(min));
1086        self
1087    }
1088
1089    /// Sets the maximum horizontal length
1090    pub fn with_hor_max(mut self, max: f32) -> Self {
1091        constrain(&mut self.hor_cons, Constraint::Max(max));
1092        self
1093    }
1094
1095    /// Sets the horizontal ratio between it and its parent
1096    pub fn with_hor_ratio(mut self, den: u16, div: u16) -> Self {
1097        constrain(&mut self.hor_cons, Constraint::Ratio(den, div));
1098        self
1099    }
1100
1101    /// An [`Iterator`] over the vertical [`Constraint`]s
1102    pub fn ver_cons(&self) -> impl Iterator<Item = Constraint> {
1103        self.ver_cons.into_iter().flatten()
1104    }
1105
1106    /// An [`Iterator`] over the horizontal [`Constraint`]s
1107    pub fn hor_cons(&self) -> impl Iterator<Item = Constraint> {
1108        self.hor_cons.into_iter().flatten()
1109    }
1110
1111    /// The constraints on a given [`Axis`]
1112    pub fn cons_on(&self, axis: Axis) -> impl Iterator<Item = Constraint> {
1113        match axis {
1114            Axis::Horizontal => self.hor_cons.into_iter().flatten(),
1115            Axis::Vertical => self.ver_cons.into_iter().flatten(),
1116        }
1117    }
1118
1119    /// Wether it is resizable in an [`Axis`]
1120    ///
1121    /// It will be resizable if there are no [`Constraint::Len`] in
1122    /// that [`Axis`].
1123    pub fn is_resizable_on(&self, axis: Axis) -> bool {
1124        let cons = match axis {
1125            Axis::Horizontal => &self.hor_cons,
1126            Axis::Vertical => &self.ver_cons,
1127        };
1128        cons.iter()
1129            .flatten()
1130            .all(|con| matches!(con, Constraint::Min(..) | Constraint::Max(..)))
1131    }
1132}
1133
1134/// A constraint used to determine the size of [`Widget`]s
1135#[derive(Debug, Clone, Copy, PartialEq)]
1136pub enum Constraint {
1137    /// Constrain this dimension to a certain length
1138    Len(f32),
1139    /// The length in this dimension must be at least this long
1140    Min(f32),
1141    /// The length in this dimension must be at most this long
1142    Max(f32),
1143    /// The length in this dimension should be this fraction of its
1144    /// parent
1145    Ratio(u16, u16),
1146}
1147
1148impl Eq for Constraint {}
1149
1150#[allow(clippy::non_canonical_partial_ord_impl)]
1151impl PartialOrd for Constraint {
1152    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1153        fn discriminant(con: &Constraint) -> usize {
1154            match con {
1155                Constraint::Len(_) => 0,
1156                Constraint::Min(_) => 1,
1157                Constraint::Max(_) => 2,
1158                Constraint::Ratio(..) => 3,
1159            }
1160        }
1161        match (self, other) {
1162            (Constraint::Len(lhs), Constraint::Len(rhs)) => lhs.partial_cmp(rhs),
1163            (Constraint::Min(lhs), Constraint::Min(rhs)) => lhs.partial_cmp(rhs),
1164            (Constraint::Max(lhs), Constraint::Max(rhs)) => lhs.partial_cmp(rhs),
1165            (Constraint::Ratio(lhs_den, lhs_div), Constraint::Ratio(rhs_den, rhs_div)) => {
1166                (lhs_den, lhs_div).partial_cmp(&(rhs_den, rhs_div))
1167            }
1168            (lhs, rhs) => discriminant(lhs).partial_cmp(&discriminant(rhs)),
1169        }
1170    }
1171}
1172
1173impl Ord for Constraint {
1174    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1175        self.partial_cmp(other).unwrap()
1176    }
1177}
1178
1179impl Constraint {
1180    /// Returns `true` if the constraint is [`Len`].
1181    ///
1182    /// [`Len`]: Constraint::Len
1183    #[must_use]
1184    pub fn is_len(&self) -> bool {
1185        matches!(self, Self::Len(..))
1186    }
1187
1188    /// Returns `true` if the constraint is [`Min`].
1189    ///
1190    /// [`Min`]: Constraint::Min
1191    #[must_use]
1192    pub fn is_min(&self) -> bool {
1193        matches!(self, Self::Min(..))
1194    }
1195
1196    /// Returns `true` if the constraint is [`Max`].
1197    ///
1198    /// [`Max`]: Constraint::Max
1199    #[must_use]
1200    pub fn is_max(&self) -> bool {
1201        matches!(self, Self::Max(..))
1202    }
1203
1204    /// Returns `true` if the constraint is [`Ratio`].
1205    ///
1206    /// [`Ratio`]: Constraint::Ratio
1207    #[must_use]
1208    pub fn is_ratio(&self) -> bool {
1209        matches!(self, Self::Ratio(..))
1210    }
1211}
1212
1213/// A direction, where a [`Widget`] will be placed in relation to
1214/// another.
1215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1216pub enum Side {
1217    /// Put the [`Widget`] above another
1218    Above,
1219    /// Put the [`Widget`] on the right
1220    Right,
1221    /// Put the [`Widget`] on the left
1222    Below,
1223    /// Put the [`Widget`] below another
1224    Left,
1225}
1226
1227impl Side {
1228    /// Which [`Axis`] this [`Side`] belongs to
1229    pub fn axis(&self) -> Axis {
1230        match self {
1231            Side::Above | Side::Below => Axis::Vertical,
1232            Side::Right | Side::Left => Axis::Horizontal,
1233        }
1234    }
1235}
1236
1237/// A corner to attach a floating [`Widget`] to and from
1238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239pub enum Corner {
1240    /// Attach on/from the top left corner
1241    TopLeft,
1242    /// Attach on/from the top
1243    Top,
1244    /// Attach on/from the top right corner
1245    TopRight,
1246    /// Attach on/from the right
1247    Right,
1248    /// Attach on/from the bottom right corner
1249    BottomRight,
1250    /// Attach on/from the bottom
1251    Bottom,
1252    /// Attach on/from the bottom left  corner
1253    BottomLeft,
1254    /// Attach on/from the left
1255    Left,
1256    /// Attach on/from the center
1257    Center,
1258}
1259
1260/// A struct representing a "visual position" on the screen
1261///
1262/// This position differs from a [`VPoint`] in the sense that it
1263/// represents three properties of a printed character:
1264///
1265/// - The x position in which it was printed;
1266/// - The amount of horizontal space it occupies;
1267/// - Wether this character is the first on the line (i.e. it wraps)
1268///
1269/// [`VPoint`]: crate::mode::VPoint
1270#[derive(Debug, Clone, Copy)]
1271pub struct Caret {
1272    /// The horizontal position in which a character was printed
1273    pub x: u32,
1274    /// The horizontal space it occupied
1275    pub len: u32,
1276    /// Wether it is the first character in the line
1277    pub wrap: bool,
1278}
1279
1280impl Caret {
1281    /// Returns a new [`Caret`]
1282    #[inline(always)]
1283    pub fn new(x: u32, len: u32, wrap: bool) -> Self {
1284        Self { x, len, wrap }
1285    }
1286}
1287
1288const fn constrain(cons: &mut [Option<Constraint>; 4], con: Constraint) {
1289    let mut i = 0;
1290
1291    while i < 4 {
1292        i += 1;
1293
1294        cons[i - 1] = match (cons[i - 1], con) {
1295            (None, _)
1296            | (Some(Constraint::Len(_)), Constraint::Len(_))
1297            | (Some(Constraint::Min(_)), Constraint::Min(_))
1298            | (Some(Constraint::Max(_)), Constraint::Max(_))
1299            | (Some(Constraint::Ratio(..)), Constraint::Ratio(..)) => Some(con),
1300            _ => continue,
1301        };
1302
1303        break;
1304    }
1305}
1306
1307/// A struct used to modify the layout of [`RawArea`]s
1308///
1309/// The end user should not have access to methods that directly
1310/// modify the layout, like [`RawArea::delete`] or
1311/// [`RawArea::bisect`], since they will modify the layout without
1312/// any coordination with the rest of Duat, so this struct is used to
1313/// "hide" those methods, in order to prevent users from directly
1314/// accessing them.
1315///
1316/// Higher lever versions of these methods are still available to the
1317/// end user, in the more controled APIs of [`Area`]
1318pub struct MutArea<'area, A: RawArea>(pub(crate) &'area A);
1319
1320impl<A: RawArea> MutArea<'_, A> {
1321    /// Bisects the [`RawArea`] in two
1322    pub fn bisect(
1323        self,
1324        specs: PushSpecs,
1325        cluster: bool,
1326        on_files: bool,
1327        cache: A::Cache,
1328    ) -> (A, Option<A>) {
1329        A::bisect(self, specs, cluster, on_files, cache)
1330    }
1331
1332    /// Calls [`RawArea::delete`] on `self`
1333    pub fn delete(self) -> Option<A> {
1334        A::delete(self)
1335    }
1336
1337    /// Calls [`RawArea::swap`] on `self`
1338    pub fn swap(self, other: &A) {
1339        A::swap(self, other);
1340    }
1341
1342    /// Calls [`RawArea::spawn_floating`] on `self`
1343    pub fn spawn_floating(self, specs: SpawnSpecs) -> Result<A, Text> {
1344        A::spawn_floating(self, specs)
1345    }
1346
1347    /// Calls [`RawArea::spawn_floating_at`] on `self`
1348    pub fn spawn_floating_at(
1349        self,
1350        specs: SpawnSpecs,
1351        at: impl TwoPoints,
1352        text: &Text,
1353        cfg: PrintCfg,
1354    ) -> Result<A, Text> {
1355        A::spawn_floating_at(self, specs, at, text, cfg)
1356    }
1357}
1358
1359impl<A: RawArea> std::ops::Deref for MutArea<'_, A> {
1360    type Target = A;
1361
1362    fn deref(&self) -> &Self::Target {
1363        self.0
1364    }
1365}
1366
1367/// A public API for [`Ui::Area`]
1368#[derive(Clone, PartialEq)]
1369pub struct Area<U: Ui> {
1370    area: U::Area,
1371}
1372
1373impl<U: Ui> std::ops::Deref for Area<U> {
1374    type Target = U::Area;
1375
1376    fn deref(&self) -> &Self::Target {
1377        &self.area
1378    }
1379}