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}