duat_term/layout/
mod.rs

1use std::{
2    cell::RefCell,
3    io::Write,
4    rc::Rc,
5    sync::{Arc, Mutex},
6};
7
8use crossterm::{
9    cursor::MoveTo,
10    queue,
11    style::{ContentStyle, SetStyle},
12};
13use duat_core::{
14    form::{Form, FormId},
15    opts::PrintOpts,
16    text::{Text, TwoPoints},
17    ui::{Axis, DynSpawnSpecs, Orientation, PushSpecs, Side, SpawnId, StaticSpawnSpecs},
18};
19use kasuari::{Constraint, WeightedRelation::*};
20
21pub use self::rect::{Deletion, Rect, recurse_length, transfer_vars};
22use crate::{
23    AreaId, Border, CONS_SPAWN_LEN_PRIO, Coords, HIDDEN_PRIO, LEN_PRIO, MANUAL_LEN_PRIO,
24    area::{Coord, PrintInfo, print_text},
25    printer::{Lines, Printer},
26};
27
28mod rect;
29
30/// The list of opened layouts, one for each window
31///
32/// Also contains a list of "free widgets", which can dynamically
33/// change which layout they belong to, which _could_ happen if the
34/// user decides to switch two widgets's [`Text`]s
35///
36/// [`Text`]: duat_core::text::Text
37#[derive(Clone, Default)]
38pub(crate) struct Layouts(Rc<RefCell<InnerLayouts>>);
39
40impl Layouts {
41    /// Adds a new layout, returning the main [`AreaId`]
42    pub fn new_layout(&self, printer: Arc<Printer>, border: Border, cache: PrintInfo) -> AreaId {
43        let layout = Layout::new(printer, border, cache);
44        let main_id = layout.main_id();
45
46        let mut layouts = self.0.borrow_mut();
47        layouts.list.push(layout);
48
49        if layouts.active_id.is_none() {
50            layouts.active_id = Some(main_id);
51        }
52
53        main_id
54    }
55
56    ////////// Layout modification
57
58    /// Push a new [`Area`] to an existing one, returning its
59    /// [`AreaId`], as well as a parent [`AreaId`], if it was created
60    ///
61    /// May return [`None`] in the situation where the `Area` of the
62    /// id was already deleted.
63    ///
64    /// [`Area`]: crate::area::Area
65    pub fn push(
66        &self,
67        target: AreaId,
68        specs: PushSpecs,
69        on_files: bool,
70        cache: PrintInfo,
71    ) -> Option<(AreaId, Option<AreaId>)> {
72        let mut layouts = self.0.borrow_mut();
73        layouts
74            .list
75            .iter_mut()
76            .find_map(|l| l.push(target, specs, on_files, cache))
77    }
78
79    /// Spawnss a new [`Area`] on an existing one, returning its
80    /// [`AreaId`]
81    ///
82    /// May return [`None`] in the situation where the `Area` of the
83    /// id was already deleted.
84    ///
85    /// [`Area`]: crate::area::Area
86    pub fn spawn_on_widget(
87        &self,
88        target: AreaId,
89        spawn_id: SpawnId,
90        specs: DynSpawnSpecs,
91        cache: PrintInfo,
92    ) -> Option<AreaId> {
93        let mut layouts = self.0.borrow_mut();
94        layouts
95            .list
96            .iter_mut()
97            .find_map(|layout| layout.spawn_on_widget(target, spawn_id, specs, cache))
98    }
99
100    /// Spawns a new [`Rect`] from a [`SpawnId`], which is supposed to
101    /// go on [`Text`]
102    ///
103    /// [`Text`]: duat_core::text::Text
104    pub fn spawn_on_text(
105        &self,
106        id: SpawnId,
107        specs: DynSpawnSpecs,
108        cache: PrintInfo,
109        win: usize,
110    ) -> AreaId {
111        let mut layouts = self.0.borrow_mut();
112        layouts.list[win].spawn_on_text(id, specs, cache)
113    }
114
115    /// Spawns a new statically positioned [`Rect`] from a [`SpawnId`]
116    pub fn spawn_static(
117        &self,
118        id: SpawnId,
119        specs: StaticSpawnSpecs,
120        cache: PrintInfo,
121        win: usize,
122    ) -> AreaId {
123        let mut layouts = self.0.borrow_mut();
124        layouts.list[win].spawn_static(id, specs, cache)
125    }
126
127    /// Deletes the [`Area`] of a given id
128    ///
129    /// Returns wether this deletion resulted in the window being
130    /// removed, and also returns every [`AreaId`] belonging to a
131    /// [`Rect`] that was removed as a consequence of this.
132    ///
133    /// [`Area`]: crate::area::Area
134    pub fn delete(&self, id: AreaId) -> (bool, Vec<AreaId>) {
135        let mut layouts = self.0.borrow_mut();
136        if let Some((i, rm_areas)) = layouts
137            .list
138            .iter_mut()
139            .enumerate()
140            .find_map(|(i, layout)| Some(i).zip(layout.delete(id)))
141        {
142            if rm_areas.contains(&layouts.list[i].main.id()) {
143                layouts.list.remove(i);
144                (true, rm_areas)
145            } else {
146                layouts.list[i].printer.update(false, false);
147                (false, rm_areas)
148            }
149        } else {
150            (false, Vec::new())
151        }
152    }
153
154    /// Swaps two [`Area`]s around
155    ///
156    /// Will also swap their clusters, if they belong to different
157    /// cluster masters
158    ///
159    /// Returns `false` if no swap occurred, either due to one or both
160    /// of `Area`s having already been deleted, or because they belong
161    /// to the same cluster master.
162    ///
163    /// [`Area`]: crate::area::Area
164    pub fn swap(&self, lhs: AreaId, rhs: AreaId) -> bool {
165        let mut inner = self.0.borrow_mut();
166        let list = &mut inner.list;
167        let (Some((l_layout_i, l_main_id)), Some((r_layout_i, r_main_id))) = (
168            list.iter()
169                .enumerate()
170                .find_map(|(i, layout)| Some(i).zip(layout.get_main_id(lhs))),
171            list.iter()
172                .enumerate()
173                .find_map(|(i, layout)| Some(i).zip(layout.get_main_id(rhs))),
174        ) else {
175            return false;
176        };
177
178        if l_main_id == r_main_id {
179            let layout = &mut list[l_layout_i];
180            let l_id = layout.get_cluster_master(lhs).unwrap_or(lhs);
181            let r_id = layout.get_cluster_master(rhs).unwrap_or(rhs);
182
183            if l_id == r_id {
184                return false;
185            }
186
187            layout.swap(l_id, r_id);
188        } else {
189            let l_p = list[l_layout_i].printer.clone();
190            let r_p = list[r_layout_i].printer.clone();
191
192            let (l_main, r_main) = if l_layout_i == r_layout_i {
193                list[l_layout_i]
194                    .get_disjoint_mains_mut(l_main_id, r_main_id)
195                    .unwrap()
196            } else {
197                let layouts_i = [l_layout_i, r_layout_i];
198                let [l_layout, r_layout] = list.get_disjoint_mut(layouts_i).unwrap();
199                let l_main = l_layout.get_mut(l_main_id).unwrap();
200                let r_main = r_layout.get_mut(r_main_id).unwrap();
201                (l_main, r_main)
202            };
203
204            let l_id = l_main.get_cluster_master(lhs).unwrap_or(lhs);
205            let r_id = r_main.get_cluster_master(rhs).unwrap_or(rhs);
206
207            let l_rect = l_main.get_mut(l_id).unwrap();
208            let r_rect = r_main.get_mut(r_id).unwrap();
209
210            transfer_vars(&l_p, &r_p, l_rect);
211            transfer_vars(&r_p, &l_p, r_rect);
212
213            std::mem::swap(l_rect, r_rect);
214
215            list[l_layout_i].reset_eqs(r_id);
216            list[r_layout_i].reset_eqs(l_id);
217        }
218
219        for layout_i in [l_layout_i, r_layout_i] {
220            list[layout_i].printer.update(false, false);
221        }
222
223        true
224    }
225
226    /// Sets the constraints on a given [`Rect`]
227    ///
228    /// Returns `false` if the [`Rect`] was deleted or if there was
229    /// nothing to be done.
230    pub fn set_constraints(
231        &self,
232        id: AreaId,
233        width: Option<f32>,
234        height: Option<f32>,
235        is_hidden: Option<bool>,
236    ) -> bool {
237        self.0
238            .borrow_mut()
239            .list
240            .iter_mut()
241            .any(|layout| layout.set_constraints(id, width, height, is_hidden))
242    }
243
244    /// Just updates the [`Printer`] for the [`AreaId`] in question
245    ///
246    /// Returns `false` if the [`Rect`] was deleted.
247    pub fn update(&self, id: AreaId) -> bool {
248        let inner = self.0.borrow();
249        if let Some(layout) = inner.list.iter().find(|layout| layout.get(id).is_some()) {
250            layout.printer.update(false, false);
251            true
252        } else {
253            false
254        }
255    }
256
257    /// Sets the [`Frame`] on a given [`Rect`]'s spawned master
258    ///
259    /// If a spawned `Rect` has multiple `Rect`s within it, then
260    /// setting the `Frame` on any of themwill change the `Frame` of
261    /// the spawned master instead, since the children of spawned
262    /// `Rect`s don't have `Frame`s
263    ///
264    /// Returns `false` if the [`Rect`] was deleted.
265    pub fn set_frame(&self, id: AreaId, mut frame: Frame) -> bool {
266        let mut inner = self.0.borrow_mut();
267        let frame_was_set = inner
268            .list
269            .iter_mut()
270            .any(|layout| layout.set_frame(id, &mut frame));
271
272        frame_was_set
273            || inner
274                .list
275                .iter()
276                .any(|layout| layout.main.get(id).is_some())
277    }
278
279    /// Removes all windows and all spawned widgets
280    pub fn reset(&self) {
281        let mut inner = self.0.borrow_mut();
282        inner.list.clear();
283        inner.active_id = None;
284    }
285
286    /// Removes a window from the list
287    pub fn remove_window(&self, win: usize) {
288        self.0.borrow_mut().list.remove(win);
289    }
290
291    /// Sets the [`PrintInfo`] of an [`AreaId`]'s [`Rect`]
292    ///
293    /// Returns `false` if the `Rect` was deleted or if it is not a
294    /// leaf node
295    pub fn set_info_of(&self, id: AreaId, new: PrintInfo) -> bool {
296        let mut inner = self.0.borrow_mut();
297        inner.list.iter_mut().any(|layout| {
298            layout
299                .get_mut(id)
300                .and_then(|rect| rect.print_info_mut())
301                .map(|info| *info = new)
302                .is_some()
303        })
304    }
305
306    ////////// Functions for printing
307
308    /// Sends lines to be printed on screen
309    pub fn send_lines(
310        &self,
311        area_id: AreaId,
312        lines: Lines,
313        spawns: impl Iterator<Item = SpawnId>,
314        observed_spawns: &[(SpawnId, Coord, u32)],
315    ) {
316        let mut inner = self.0.borrow_mut();
317        let layout = inner
318            .list
319            .iter_mut()
320            .find(|layout| layout.get(area_id).is_some())
321            .unwrap();
322
323        let mut revealed_at_least_one = false;
324        for spawn_id in spawns {
325            if let Some((_, rect)) = layout.spawned.iter().find(|(info, _)| info.id == spawn_id) {
326                let hidden = !observed_spawns.iter().any(|(id, ..)| id == &spawn_id);
327                recurse_set_hidden(layout, rect.id(), hidden);
328                revealed_at_least_one = !hidden;
329            }
330        }
331
332        if revealed_at_least_one {
333            layout.printer.update(false, false);
334        }
335
336        if let Some((info, _)) = layout
337            .spawned
338            .iter()
339            .find(|(_, rect)| rect.get(area_id).is_some())
340        {
341            layout
342                .printer
343                .send_spawn_lines(area_id, info.id, lines, &info.frame);
344        } else {
345            layout.printer.send_lines(lines);
346        }
347
348        for (id, coord, len) in observed_spawns.iter().copied() {
349            if let Some((i, _rect)) = inner.list.iter_mut().enumerate().find_map(|(i, layout)| {
350                layout
351                    .spawned
352                    .iter_mut()
353                    .find_map(|(info, rect)| (info.id == id).then_some((i, rect)))
354            }) {
355                inner.list[i].printer.move_spawn_to(id, coord, len);
356                inner.list[i].printer.update(false, false);
357            }
358        }
359    }
360
361    /// Sets the active [`AreaId`]
362    ///
363    /// Does nothing if the [`Rect`] of that `AreaId` was deleted.
364    pub fn set_active_id(&self, id: AreaId) {
365        let mut inner = self.0.borrow_mut();
366        if inner.list.iter().any(|layout| layout.get(id).is_some()) {
367            inner.active_id = Some(id);
368        }
369    }
370
371    ////////// Querying functions
372
373    /// The active [`AreaId`]
374    pub fn get_active_id(&self) -> AreaId {
375        self.0.borrow().active_id.unwrap()
376    }
377
378    /// Reads the [`Rect`] of an [`AreaId`], if it still exists
379    ///
380    /// Can return [`None`] if the [`Rect`] has deleted.
381    pub fn inspect<Ret>(&self, id: AreaId, f: impl FnOnce(&Rect, &Layout) -> Ret) -> Option<Ret> {
382        let inner = self.0.borrow();
383        inner
384            .list
385            .iter()
386            .find_map(|layout| layout.get(id).zip(Some(layout)))
387            .map(|(rect, layout)| f(rect, layout))
388    }
389
390    /// Get the [`Coords`] of an [`AreaId`]'s [`Rect`]
391    ///
392    /// Also returns wether or not they have changed.
393    ///
394    /// Returns [`None`] if the `Rect` in question was deleted.
395    pub fn coords_of(&self, id: AreaId, is_printing: bool) -> Option<Coords> {
396        let inner = self.0.borrow();
397        inner
398            .list
399            .iter()
400            .find_map(|layout| layout.coords_of(id, is_printing))
401    }
402
403    /// Get the [`PrintInfo`] of an [`AreaId`]'s [`Rect`]
404    ///
405    /// Returns [`None`] if the `Rect` was deleted or if it is not a
406    /// leaf node
407    pub fn get_info_of(&self, id: AreaId) -> Option<PrintInfo> {
408        let inner = self.0.borrow();
409        inner
410            .list
411            .iter()
412            .find_map(|layout| layout.get(id))
413            .and_then(|rect| rect.print_info())
414            .cloned()
415    }
416}
417
418#[derive(Default)]
419struct InnerLayouts {
420    list: Vec<Layout>,
421    active_id: Option<AreaId>,
422}
423
424/// The overrall structure of a window on `duat_term`.
425///
426/// The [`Layout`] handles all of the [`Rect`]s inside of it,
427/// including all of the [`Variable`]s and
428/// [`Constraint`](Constraint)s that define said [`Rect`]s.
429/// All external interactions seeking to change these values do so
430/// through the [`Layout`].
431///
432/// The [`Layout`] also handles the [`Edge`]s that are supposed to be
433/// printed to the screen.
434///
435/// The [`Layout`] will also hold floating [`Rect`]s, once those
436/// become a thing.
437pub struct Layout {
438    main: Rect,
439    spawned: Vec<(SpawnInfo, Rect)>,
440    printer: Arc<Printer>,
441}
442
443impl Layout {
444    /// Returns a new instance of [`Layout`], applying a given
445    /// [`Border`] to all inner [`Rect`]s.
446    pub fn new(printer: Arc<Printer>, border: Border, cache: PrintInfo) -> Self {
447        let main = Rect::new_main(&printer, border, cache);
448        Layout { main, spawned: Vec::new(), printer }
449    }
450
451    /// The index of the main [`Rect`], which holds all (non floating)
452    /// others.
453    pub fn main_id(&self) -> AreaId {
454        self.main.id()
455    }
456
457    /// Pushes a new [`Area`] into an existing one
458    ///
459    /// Returns a new [`AreaId`] for the child, and a possible new
460    /// `AreaId` for the parent, if one was created.
461    ///
462    /// Will return [`None`] if the targeted [`AreaId`] is not part of
463    /// this
464    ///
465    /// [`Area`]: super::Area
466    fn push(
467        &mut self,
468        target: AreaId,
469        specs: PushSpecs,
470        on_files: bool,
471        cache: PrintInfo,
472    ) -> Option<(AreaId, Option<AreaId>)> {
473        self.main
474            .push(&self.printer, specs, target, on_files, cache, None)
475            .or_else(|| {
476                self.spawned.iter_mut().find_map(|(info, rect)| {
477                    rect.push(&self.printer, specs, target, on_files, cache, Some(info))
478                })
479            })
480    }
481
482    /// Deletes the [`Rect`] of the given [`AreaId`]
483    ///
484    /// Returns a [`Vec`] of every leaf `AreaId` that was deleted by
485    /// this action. This doesn't include the main [`AreaId`], since
486    /// that one can't be deleted from within [`Layout`]
487    ///
488    /// Returns [`None`] if this `Layout` does not contain the
489    /// `AreaId`'s `Rect`.
490    ///
491    /// [`Area`]: crate::area::Area
492    fn delete(&mut self, id: AreaId) -> Option<Vec<AreaId>> {
493        let ret = if let Some(deletion) = self.main.delete(&self.printer, id) {
494            if let Deletion::Child(rect, cons, rm_list) = deletion {
495                (rect, cons, rm_list)
496            } else {
497                self.printer.remove_rect(&mut self.main);
498
499                return Some(remove_dependents(
500                    &mut self.main,
501                    &mut self.spawned,
502                    &self.printer,
503                    Vec::new(),
504                ));
505            }
506        } else if let Some((i, deletion)) = self
507            .spawned
508            .iter_mut()
509            .enumerate()
510            .find_map(|(i, (_, rect))| Some(i).zip(rect.delete(&self.printer, id)))
511        {
512            if let Deletion::Child(rect, cons, rm_list) = deletion {
513                (rect, cons, rm_list)
514            } else {
515                let (mut info, mut rect) = self.spawned.remove(i);
516                self.printer.remove_rect(&mut rect);
517                self.printer.remove_eqs(info.cons.drain());
518                self.printer.remove_spawn_info(info.id);
519
520                return Some(remove_dependents(
521                    &mut rect,
522                    &mut self.spawned,
523                    &self.printer,
524                    Vec::new(),
525                ));
526            }
527        } else {
528            return None;
529        };
530
531        let (mut rect, mut cons, rm_list) = ret;
532
533        self.printer.remove_eqs(cons.drain());
534
535        Some(remove_dependents(
536            &mut rect,
537            &mut self.spawned,
538            &self.printer,
539            rm_list,
540        ))
541    }
542
543    /// Swaps the [`Rect`]s of two [`AreaId`]s
544    fn swap(&mut self, id0: AreaId, id1: AreaId) {
545        self.main.swap(&self.printer, id0, id1);
546    }
547
548    /// Spawns a new [`Rect`] around another, returning the [`AreaId`]
549    fn spawn_on_widget(
550        &mut self,
551        target: AreaId,
552        id: SpawnId,
553        specs: DynSpawnSpecs,
554        cache: PrintInfo,
555    ) -> Option<AreaId> {
556        let ((rect, cons), target_spawn_id) = [(&mut self.main, None, None)]
557            .into_iter()
558            .chain(
559                self.spawned
560                    .iter_mut()
561                    .map(|(info, rect)| (rect, Some(&info.frame), Some(info.id))),
562            )
563            .find_map(|(rect, frame, target_spawn_id)| {
564                rect.new_spawned_on_widget(id, specs, target, &self.printer, cache, frame)
565                    .map(|ret| (ret, target_spawn_id))
566            })?;
567
568        let area_id = rect.id();
569        let orientation = specs.orientation;
570
571        self.spawned.push((
572            SpawnInfo {
573                id,
574                spec: SpawnSpec::Dynamic(orientation, target_spawn_id),
575                cons,
576                frame: Default::default(),
577            },
578            rect,
579        ));
580
581        Some(area_id)
582    }
583
584    /// Spawns a new [`Rect`] from a [`SpawnId`], which is supposed to
585    /// go on [`Text`]
586    ///
587    /// [`Text`]: duat_core::text::Text
588    fn spawn_on_text(&mut self, id: SpawnId, specs: DynSpawnSpecs, cache: PrintInfo) -> AreaId {
589        let (rect, cons) =
590            Rect::new_spawned_on_text(&self.printer, id, self.main.border(), cache, specs);
591        let rect_id = rect.id();
592
593        self.spawned.push((
594            SpawnInfo {
595                id,
596                spec: SpawnSpec::Dynamic(specs.orientation, None),
597                cons,
598                frame: Default::default(),
599            },
600            rect,
601        ));
602
603        rect_id
604    }
605
606    fn spawn_static(&mut self, id: SpawnId, specs: StaticSpawnSpecs, cache: PrintInfo) -> AreaId {
607        let (rect, cons, orig_max) =
608            Rect::new_static_spawned(&self.printer, id, self.main.border(), cache, specs);
609        let rect_id = rect.id();
610
611        self.spawned.push((
612            SpawnInfo {
613                id,
614                spec: SpawnSpec::Static {
615                    top_left: specs.top_left,
616                    fractional_repositioning: specs.fractional_repositioning,
617                    orig_max,
618                },
619                cons,
620                frame: Default::default(),
621            },
622            rect,
623        ));
624
625        rect_id
626    }
627
628    /// Sets the [`Frame`] for an [`Area`], if it's a spawned one
629    ///
630    /// Note that setting the frame for an `Area` pushed onto an
631    /// already existing spawned `Area` will just set the frame for
632    /// both.
633    ///
634    /// [`Area`]: super::Area
635    pub fn set_frame(&mut self, id: AreaId, frame: &mut Frame) -> bool {
636        let Some((info, rect)) = self
637            .spawned
638            .iter_mut()
639            .find(|(_, rect)| rect.get(id).is_some())
640        else {
641            return false;
642        };
643
644        if info.frame == *frame {
645            return true;
646        }
647
648        info.frame = std::mem::take(frame);
649
650        match info.spec {
651            SpawnSpec::Static {
652                top_left,
653                fractional_repositioning,
654                orig_max,
655            } => {
656                let width = recurse_length(rect, &info.cons, Axis::Horizontal).unwrap() as f32;
657                let height = recurse_length(rect, &info.cons, Axis::Vertical).unwrap() as f32;
658
659                rect.set_static_spawned_eqs(
660                    &self.printer,
661                    orig_max,
662                    StaticSpawnSpecs {
663                        top_left,
664                        size: duat_core::ui::Coord::new(width, height),
665                        hidden: false,
666                        fractional_repositioning,
667                    },
668                    &info.frame,
669                );
670            }
671            SpawnSpec::Dynamic(orientation, parent_spawn_id) => {
672                let specs = DynSpawnSpecs { orientation, ..Default::default() };
673                let (deps, tl, br) = self.printer.get_spawn_info(info.id).unwrap();
674                rect.set_dyn_spawned_eqs(&self.printer, specs, deps, tl, br, &info.frame);
675
676                let info = self
677                    .spawned
678                    .iter()
679                    .find_map(|(info, rect)| rect.get(id).and(Some(info)))
680                    .unwrap();
681
682                let parent_frame = self.spawned.iter().find_map(|(info, _)| {
683                    (Some(info.id) == parent_spawn_id).then_some(&info.frame)
684                });
685
686                self.printer.set_frame(info.id, &info.frame, parent_frame);
687            }
688        }
689
690        self.printer.update(false, true);
691
692        true
693    }
694
695    /// Sets the constraints on a given [`Rect`]
696    ///
697    /// Returns `false` if this `Layout` does not contain the
698    /// [`AreaId`]'s [`Rect`].
699    pub fn set_constraints(
700        &mut self,
701        id: AreaId,
702        width: Option<f32>,
703        height: Option<f32>,
704        is_hidden: Option<bool>,
705    ) -> bool {
706        let is_eq = |cons: &mut Constraints| {
707            width.is_none_or(|w| Some(w) == cons.on(Axis::Horizontal))
708                && height.is_none_or(|h| Some(h) == cons.on(Axis::Vertical))
709                && is_hidden.is_none_or(|ih| ih == cons.is_hidden)
710        };
711
712        let get_new_cons = |main: &Rect, mut cons: Constraints| {
713            let old_eqs = cons.replace(width, height, is_hidden);
714
715            let rect = main.get(id).unwrap();
716            let (_, parent) = main.get_parent(id).unzip();
717
718            let new_eqs = cons.apply(rect, parent);
719            self.printer.replace(old_eqs, new_eqs);
720            cons
721        };
722
723        if let Some(cons) = self.main.get_constraints_mut(id) {
724            if is_eq(cons) {
725                return true;
726            }
727            let cons = cons.clone();
728            *self.main.get_constraints_mut(id).unwrap() = get_new_cons(&self.main, cons);
729            self.printer.update(false, false);
730
731            true
732        } else if let Some((i, cons)) =
733            self.spawned
734                .iter_mut()
735                .enumerate()
736                .find_map(|(i, (info, rect))| {
737                    (rect.id() == id)
738                        .then_some((i, &mut info.cons))
739                        .or_else(|| Some(i).zip(rect.get_constraints_mut(id)))
740                })
741        {
742            if is_eq(cons) {
743                return true;
744            }
745            let cons = cons.clone();
746
747            let (SpawnInfo { cons: main_cons, .. }, main) = &mut self.spawned[i];
748            if main.id() == id {
749                *main_cons = get_new_cons(main, cons);
750            } else {
751                *main.get_constraints_mut(id).unwrap() = get_new_cons(main, cons);
752            }
753
754            if is_hidden == Some(true) || [width, height].contains(&Some(0.0)) {
755                self.printer.clear_spawn(id);
756            }
757
758            let (SpawnInfo { id, spec: orientation, cons, .. }, rect) = &self.spawned[i];
759            if let SpawnSpec::Dynamic(orientation, _) = orientation {
760                let len = recurse_length(rect, cons, orientation.axis());
761                self.printer.set_spawn_len(*id, len.map(|len| len as f64));
762            }
763            self.printer.update(false, true);
764
765            true
766        } else {
767            false
768        }
769    }
770
771    /// Resets the equalities of the [`Rect`] of an [`AreaId`]
772    fn reset_eqs(&mut self, id: AreaId) {
773        [&mut self.main]
774            .into_iter()
775            .chain(self.spawned.iter_mut().map(|(_, rect)| rect))
776            .any(|rect| rect.reset_eqs(&self.printer, id));
777    }
778
779    ////////// Getters
780
781    /// Gets the [`Rect`] of and [`AreaId`], if found
782    pub fn get(&self, id: AreaId) -> Option<&Rect> {
783        [&self.main]
784            .into_iter()
785            .chain(self.spawned.iter().map(|(_, rect)| rect))
786            .find_map(|rect| rect.get(id))
787    }
788
789    /// Gets the parent [`Rect`] of and [`AreaId`], as well as its
790    /// index on the list of children, if found
791    pub fn get_parent(&self, id: AreaId) -> Option<(usize, &Rect)> {
792        [&self.main]
793            .into_iter()
794            .chain(self.spawned.iter().map(|(_, rect)| rect))
795            .find_map(|rect| rect.get_parent(id))
796    }
797
798    /// Gets the cluster master [`AreaId`] of another, if found
799    pub fn get_cluster_master(&self, id: AreaId) -> Option<AreaId> {
800        [&self.main]
801            .into_iter()
802            .chain(self.spawned.iter().map(|(_, rect)| rect))
803            .find_map(|rect| rect.get_cluster_master(id))
804    }
805
806    /// Get the main [`Rect`] that contains this `AreaId`'s
807    fn get_main_id(&self, id: AreaId) -> Option<AreaId> {
808        [&self.main]
809            .into_iter()
810            .chain(self.spawned.iter().map(|(_, rect)| rect))
811            .find_map(|rect| rect.get(id).is_some().then_some(rect.id()))
812    }
813
814    /// Gets the mut [`Rect`] of an [`AreaId`]
815    fn get_mut(&mut self, id: AreaId) -> Option<&mut Rect> {
816        [&mut self.main]
817            .into_iter()
818            .chain(self.spawned.iter_mut().map(|(_, rect)| rect))
819            .find_map(|rect| rect.get_mut(id))
820    }
821
822    ////////// Querying functions
823
824    /// Get the maximum value for this `Layout`
825    pub fn max_value(&self) -> crate::area::Coord {
826        self.printer.max_value()
827    }
828
829    /// Get the [`Coords`] of an [`AreaId`]'s [`Rect`]
830    ///
831    /// Also returns wether or not they have changed.
832    ///
833    /// Returns [`None`] if this `Layout` does not contain the
834    /// [`AreaId`]'s [`Rect`].
835    fn coords_of(&self, id: AreaId, is_printing: bool) -> Option<Coords> {
836        let rect = self.get(id)?;
837        Some(self.printer.coords(rect.var_points(), is_printing))
838    }
839
840    /// Gets two disjointed main [`Rect`]s
841    ///
842    /// Fails if they can't be found, or if they are the same
843    /// [`AreaId`]s
844    fn get_disjoint_mains_mut(
845        &mut self,
846        l_main_id: AreaId,
847        r_main_id: AreaId,
848    ) -> Option<(&mut Rect, &mut Rect)> {
849        let l_con = |rect: &Rect| rect.id() == l_main_id;
850        let r_con = |rect: &Rect| rect.id() == r_main_id;
851
852        if l_main_id == self.main.id() {
853            let (_, r_main) = self.spawned.iter_mut().find(|(_, rect)| r_con(rect))?;
854            Some((&mut self.main, r_main))
855        } else if r_main_id == self.main.id() {
856            let (_, l_main) = self.spawned.iter_mut().find(|(_, rect)| l_con(rect))?;
857            Some((l_main, &mut self.main))
858        } else {
859            let l_i = self.spawned.iter().position(|(_, rect)| l_con(rect))?;
860            let r_i = self.spawned.iter().position(|(_, rect)| r_con(rect))?;
861
862            let [(_, l_rect), (_, r_rect)] = self.spawned.get_disjoint_mut([l_i, r_i]).ok()?;
863            Some((l_rect, r_rect))
864        }
865    }
866}
867
868/// A listed main spawned [`Rect`]
869#[derive(Clone)]
870struct SpawnInfo {
871    id: SpawnId,
872    spec: SpawnSpec,
873    cons: Constraints,
874    frame: Frame,
875}
876
877/// The specifics of a spawned [`Rect`]
878#[derive(Debug, Clone, Copy)]
879enum SpawnSpec {
880    Static {
881        top_left: duat_core::ui::Coord,
882        fractional_repositioning: Option<bool>,
883        orig_max: Coord,
884    },
885    Dynamic(Orientation, Option<SpawnId>),
886}
887
888/// A list of [`Constraint`] for [`Rect`]s to follow.
889///
890/// These [`Constraint`]s are specifically not related to a [`Rect`]s
891/// location, in relation to other [`Rect`]s. They instead deal with
892/// two things, affecting a [`Rect`]s length in its parent's [`Axis`]:
893///
894/// - `defined`: A [`Constraint`], provided by the user, which details
895///   specific requests for the length of a [`Rect`].
896/// - `ratio`: A [`Constraint`] which details the ratio between the
897///   length of this [`Rect`] and the length of the [`Rect`] that
898///   follows it, if there is any.
899///
900/// Both of these constraints are optional, and are meant to be
901/// replaceable at runtime.
902///
903/// [`Constraint`]: Constraint
904#[derive(Default, Debug, Clone)]
905pub struct Constraints {
906    hor_con: Option<Constraint>,
907    ver_con: Option<Constraint>,
908    width: Option<(f32, bool)>,
909    height: Option<(f32, bool)>,
910    is_hidden: bool,
911}
912
913impl Constraints {
914    /// Returns a new instance of [`Constraints`]
915    ///
916    /// Will also add all equalities needed to make this constraint
917    /// work.
918    ///
919    /// This operation can fail if the `parent` in question can't be
920    /// found in the `main` [`Rect`]
921    fn new(
922        p: &Printer,
923        [width, height]: [Option<f32>; 2],
924        is_hidden: bool,
925        rect: &Rect,
926        parent: Option<&Rect>,
927    ) -> Self {
928        let width = width.zip(Some(false));
929        let height = height.zip(Some(false));
930        let is_spawned = rect.spawn_id().is_some();
931        let [ver_con, hor_con] = get_cons([width, height], rect, is_hidden, is_spawned, parent);
932
933        p.add_eqs(ver_con.clone().into_iter().chain(hor_con.clone()));
934
935        Self {
936            hor_con,
937            ver_con,
938            width,
939            height,
940            is_hidden,
941        }
942    }
943
944    /// Replaces the inner elements of the `Constraints`
945    pub fn replace(
946        &mut self,
947        width: Option<f32>,
948        height: Option<f32>,
949        is_hidden: Option<bool>,
950    ) -> Vec<Constraint> {
951        let hor_con = self.hor_con.take();
952        let ver_con = self.ver_con.take();
953        // A replacement means manual constraining, which is prioritized.
954
955        self.width = width.map(|w| (w, true)).or(self.width);
956        self.height = height.map(|h| (h, true)).or(self.height);
957        self.is_hidden = is_hidden.unwrap_or(self.is_hidden);
958
959        hor_con.into_iter().chain(ver_con).collect()
960    }
961
962    /// Reuses [`self`] in order to constrain a new child
963    pub fn apply(&mut self, rect: &Rect, parent: Option<&Rect>) -> Vec<Constraint> {
964        let constraints = [self.width, self.height];
965        let is_spawned = rect.spawn_id().is_some();
966        let [ver_con, hor_con] = get_cons(constraints, rect, self.is_hidden, is_spawned, parent);
967        let new_eqs = ver_con.clone().into_iter().chain(hor_con.clone());
968
969        self.ver_con = ver_con;
970        self.hor_con = hor_con;
971
972        new_eqs.collect()
973    }
974
975    pub fn drain(&mut self) -> impl Iterator<Item = Constraint> {
976        self.ver_con.take().into_iter().chain(self.hor_con.take())
977    }
978
979    pub fn on(&self, axis: Axis) -> Option<f32> {
980        match axis {
981            Axis::Horizontal => self.width.map(|(w, _)| w),
982            Axis::Vertical => self.height.map(|(h, _)| h),
983        }
984    }
985
986    /// Whether or not [`self`] has flexibility in terms of its
987    /// length.
988    fn is_resizable_on(&self, axis: Axis) -> bool {
989        self.on(axis).is_none()
990    }
991}
992
993/// A frame around a spawned [`Area`]
994///
995/// This can be configured on an `Area` per `Area` basis, or you can
996/// set a global configuration. In that case [`Widget`]s should strive
997/// to respect the `style` field, even if they override the other
998/// fields as necessary.
999///
1000/// [`Area`]: super::Area
1001/// [`Widget`]: duat_core::ui::Widget
1002#[derive(Default, Clone)]
1003#[allow(clippy::type_complexity)]
1004pub struct Frame {
1005    /// Show a frame above
1006    pub above: bool,
1007    /// Show a frame below
1008    pub below: bool,
1009    /// Show a frame on the left
1010    pub left: bool,
1011    /// Show a frame on the right
1012    pub right: bool,
1013    /// The `Frame`'s style
1014    pub style: Option<FrameStyle>,
1015    /// Which [`FormId`] should be used for the frame
1016    pub form: Option<FormId>,
1017    /// The [`Text`] functions for each side
1018    ///
1019    /// You should call [`Frame::set_text`] to set each of these.
1020    ///
1021    /// The order is: top, right, bottom, left
1022    #[doc(hidden)]
1023    pub side_texts: [Option<Arc<dyn Fn(usize) -> Text>>; 4],
1024}
1025
1026impl Frame {
1027    /// An [`Iterator`] over the sides of the `Frame`
1028    ///
1029    /// The order is: above, below, left, right
1030    pub fn sides(&self) -> impl Iterator<Item = bool> {
1031        [self.above, self.below, self.left, self.right].into_iter()
1032    }
1033
1034    /// Draws the frame
1035    pub fn draw(
1036        &self,
1037        stdout: &mut std::io::BufWriter<std::fs::File>,
1038        coords: Coords,
1039        form: Form,
1040        max: Coord,
1041    ) {
1042        let above = self.above && coords.tl.y > 0;
1043        let right = self.right && coords.br.x < max.x;
1044        let below = self.below && coords.br.y < max.y;
1045        let left = self.left && coords.tl.x > 0;
1046
1047        let sides = [
1048            above.then_some(Side::Above),
1049            right.then_some(Side::Right),
1050            below.then_some(Side::Below),
1051            left.then_some(Side::Left),
1052        ];
1053        let style = self
1054            .style
1055            .clone()
1056            .unwrap_or_else(|| DEFAULT_FRAME_STYLE.lock().unwrap().clone());
1057
1058        for side in sides.into_iter().flatten() {
1059            style.draw_side(stdout, coords, form.style, side);
1060        }
1061
1062        let corners = [
1063            (above && right).then_some((
1064                Coord::new(coords.br.x, coords.tl.y - 1),
1065                [Side::Above, Side::Right],
1066            )),
1067            (below && right).then_some((
1068                Coord::new(coords.br.x, coords.br.y),
1069                [Side::Below, Side::Right],
1070            )),
1071            (below && left).then_some((
1072                Coord::new(coords.tl.x - 1, coords.br.y),
1073                [Side::Below, Side::Left],
1074            )),
1075            (above && left).then_some((
1076                Coord::new(coords.tl.x - 1, coords.tl.y - 1),
1077                [Side::Above, Side::Left],
1078            )),
1079        ];
1080
1081        for (coord, sides) in corners.into_iter().flatten() {
1082            style.draw_corner(stdout, coord, form.style, sides);
1083        }
1084
1085        let text_fn = |tl_x: u32, tl_y: u32, br_x: u32, br_y: u32| {
1086            move |text_fn: &Arc<dyn Fn(usize) -> Text>| {
1087                let text = text_fn((br_x - tl_x).max(br_y - tl_y) as usize);
1088                (
1089                    Coords::new(Coord::new(tl_x, tl_y), Coord::new(br_x, br_y)),
1090                    text,
1091                )
1092            }
1093        };
1094
1095        let texts = [
1096            self.side_texts[0].as_ref().filter(|_| above).map(text_fn(
1097                coords.tl.x,
1098                coords.tl.y - 1,
1099                coords.br.x,
1100                coords.tl.y,
1101            )),
1102            self.side_texts[1].as_ref().filter(|_| right).map(text_fn(
1103                coords.br.x,
1104                coords.tl.y,
1105                coords.br.x + 1,
1106                coords.br.y,
1107            )),
1108            self.side_texts[2].as_ref().filter(|_| below).map(text_fn(
1109                coords.tl.x,
1110                coords.br.y,
1111                coords.br.x,
1112                coords.br.y + 1,
1113            )),
1114            self.side_texts[3].as_ref().filter(|_| left).map(text_fn(
1115                coords.tl.x - 1,
1116                coords.tl.y,
1117                coords.tl.x,
1118                coords.br.y,
1119            )),
1120        ];
1121
1122        let opts = PrintOpts { wrap_lines: true, ..PrintOpts::default() };
1123
1124        for (coords, text) in texts.into_iter().flatten() {
1125            let print_space = |lines: &mut Lines, len| {
1126                if len > 0 {
1127                    write!(lines, "\x1b[{len}C").unwrap()
1128                }
1129            };
1130
1131            if let Some((lines, _)) = print_text(
1132                (&text, opts, duat_core::form::painter_with_mask("title")),
1133                (coords, max),
1134                (false, TwoPoints::default(), 0),
1135                print_space,
1136                |lines, _, _| _ = lines.flush(),
1137                print_space,
1138            ) {
1139                for y in coords.tl.y..coords.br.y {
1140                    let (line, _) = lines.on(y).unwrap();
1141                    queue!(stdout, MoveTo(coords.tl.x as u16, y as u16)).unwrap();
1142                    stdout.write_all(line).unwrap();
1143                }
1144            }
1145        }
1146    }
1147
1148    /// Prints a [`Side`] as [`Text`]
1149    ///
1150    /// You can use this to, for example, set a title for your widget.
1151    /// This method takes in a function, which produces a `Text` from
1152    /// the length of the corresponding `Side`.
1153    pub fn set_text(&mut self, side: Side, text_fn: impl Fn(usize) -> Text + 'static) {
1154        match side {
1155            Side::Above => self.side_texts[0] = Some(Arc::new(text_fn)),
1156            Side::Right => self.side_texts[1] = Some(Arc::new(text_fn)),
1157            Side::Below => self.side_texts[2] = Some(Arc::new(text_fn)),
1158            Side::Left => self.side_texts[3] = Some(Arc::new(text_fn)),
1159        }
1160    }
1161}
1162
1163impl PartialEq for Frame {
1164    fn eq(&self, other: &Self) -> bool {
1165        self.above == other.above
1166            && self.below == other.below
1167            && self.left == other.left
1168            && self.right == other.right
1169            && self.style == other.style
1170            && self.form == other.form
1171            && self
1172                .side_texts
1173                .iter()
1174                .zip(other.side_texts.iter())
1175                .all(|(lhs, rhs)| match (lhs, rhs) {
1176                    (None, None) => true,
1177                    (Some(lhs), Some(rhs)) => Arc::ptr_eq(lhs, rhs),
1178                    _ => false,
1179                })
1180    }
1181}
1182
1183impl Eq for Frame {}
1184
1185/// The style for a spawned [`Area`]'s [`Frame`]
1186///
1187/// The default is [`FrameStyle::Regular`], which makes use of
1188/// characters like `─`, `│`, `┐`
1189///
1190/// [`Area`]: super::Area
1191#[derive(Default, Clone, PartialEq, Eq)]
1192pub enum FrameStyle {
1193    /// Uses `─`, `│`, `┐`
1194    #[default]
1195    Regular,
1196    /// Uses `━`, `┃`, `┓`
1197    Thick,
1198    /// Uses `╌`, `╎`, `┐`
1199    Dashed,
1200    /// Uses `╍`, `╏`, `┓`
1201    ThickDashed,
1202    /// Uses `═`, `║`, `╗`
1203    Double,
1204    /// Uses `─`, `│`, `╮`
1205    Rounded,
1206    /// Uses `▄`, `▌`, `▖`
1207    Halved,
1208    /// Uses `▏`, `▔`, `🭾`
1209    ThinBlock,
1210    /// Uses `-`, `|`, `+`
1211    Ascii,
1212    /// Uses `char` for all positions
1213    Custom {
1214        /// The [`char`] to use for each side
1215        ///
1216        /// The order is: top, right, bottom, left
1217        sides: [char; 4],
1218        /// The [`char`] to use for the corners
1219        ///
1220        /// The order is: top-right, bottom-right, bottom-left,
1221        /// top-left
1222        corners: [char; 4],
1223        /// The [`char`] to use when two [`Area`]s with the same
1224        /// `FrameStyle` come in contact perpendicularly
1225        ///
1226        /// This is just like `sides`, but the "side" represents
1227        /// which line isn't included.
1228        ///
1229        /// ```text
1230        ///   ┌────┐
1231        ///   │    │
1232        ///   │ W1 │<- Right side
1233        ///   │    │
1234        /// ┌─┴────┤<- Right t_merge
1235        /// │  W2  │
1236        /// └──────┘
1237        /// ```
1238        ///
1239        /// The order is: top, right, bottom, left
1240        ///
1241        /// [`Area`]: super::Area
1242        t_mergers: Option<[char; 4]>,
1243        /// The [`char`] to use when a merge happens on all sides,
1244        /// on [`FrameStyle::Regular`] for example, this is `┼`
1245        x_merger: Option<char>,
1246    },
1247}
1248
1249impl FrameStyle {
1250    /// Draws the `FrameStyle`
1251    pub fn draw_side(
1252        &self,
1253        stdout: &mut std::io::BufWriter<std::fs::File>,
1254        coords: Coords,
1255        style: ContentStyle,
1256        side: Side,
1257    ) {
1258        let mut char_str = [b'\0'; 4];
1259        let char = match (side, self) {
1260            (Side::Above | Side::Below, Self::Regular | Self::Rounded) => "─",
1261            (Side::Above | Side::Below, Self::Thick) => "━",
1262            (Side::Above | Side::Below, Self::Dashed) => "┄",
1263            (Side::Above | Side::Below, Self::ThickDashed) => "┅",
1264            (Side::Above | Side::Below, Self::Double) => "═",
1265            (Side::Above | Side::Below, Self::Ascii) => "-",
1266            (Side::Right | Side::Left, Self::Regular | Self::Rounded) => "│",
1267            (Side::Right | Side::Left, Self::Thick) => "┃",
1268            (Side::Right | Side::Left, Self::Dashed) => "┆",
1269            (Side::Right | Side::Left, Self::ThickDashed) => "┇",
1270            (Side::Right | Side::Left, Self::Double) => "║",
1271            (Side::Right | Side::Left, Self::Ascii) => "|",
1272            (Side::Right | Side::Left, Self::Halved) => "█",
1273            (Side::Above, Self::Halved) => "▄",
1274            (Side::Below, Self::Halved) => "▀",
1275            (Side::Above, Self::ThinBlock) => "▔",
1276            (Side::Right, Self::ThinBlock) => "▕",
1277            (Side::Below, Self::ThinBlock) => "▁",
1278            (Side::Left, Self::ThinBlock) => "▏",
1279            (side, Self::Custom { sides, .. }) => match side {
1280                Side::Above => sides[0].encode_utf8(&mut char_str),
1281                Side::Right => sides[1].encode_utf8(&mut char_str),
1282                Side::Below => sides[2].encode_utf8(&mut char_str),
1283                Side::Left => sides[3].encode_utf8(&mut char_str),
1284            },
1285        };
1286
1287        match side {
1288            Side::Left | Side::Right => {
1289                let x = if side == Side::Left {
1290                    coords.tl.x as u16 - 1
1291                } else {
1292                    coords.br.x as u16
1293                };
1294
1295                for y in coords.tl.y as u16..coords.br.y as u16 {
1296                    queue!(stdout, MoveTo(x, y), SetStyle(style),).unwrap();
1297                    write!(stdout, "{char}").unwrap();
1298                }
1299            }
1300            Side::Above | Side::Below => {
1301                let y = if side == Side::Above {
1302                    coords.tl.y as u16 - 1
1303                } else {
1304                    coords.br.y as u16
1305                };
1306
1307                queue!(stdout, MoveTo(coords.tl.x as u16, y), SetStyle(style),).unwrap();
1308                for _ in 0..(coords.br.x - coords.tl.x) {
1309                    write!(stdout, "{char}").unwrap();
1310                }
1311            }
1312        }
1313    }
1314
1315    pub fn draw_corner(
1316        &self,
1317        stdout: &mut std::io::BufWriter<std::fs::File>,
1318        coord: Coord,
1319        style: ContentStyle,
1320        sides: [Side; 2],
1321    ) {
1322        use Side::*;
1323
1324        let mut char_str = [b'\0'; 4];
1325        let char = match (sides, self) {
1326            ([Above, Right] | [Right, Above], Self::Regular | Self::Dashed) => "┐",
1327            ([Above, Right] | [Right, Above], Self::Thick | Self::ThickDashed) => "┓",
1328            ([Above, Right] | [Right, Above], Self::Double) => "╗",
1329            ([Above, Right] | [Right, Above], Self::Rounded) => "╮",
1330            ([Above, Right | Left] | [Right | Left, Above], Self::Halved) => "▄",
1331            ([Above, Right] | [Right, Above], Self::ThinBlock) => "🭾",
1332            ([Above, Left] | [Left, Above], Self::Regular) => "┌",
1333            ([Above, Left] | [Left, Above], Self::Thick | Self::ThickDashed) => "┏",
1334            ([Above, Left] | [Left, Above], Self::Double) => "╔",
1335            ([Above, Left] | [Left, Above], Self::Rounded) => "╭",
1336            ([Above, Left] | [Left, Above], Self::ThinBlock) => "🭽",
1337            ([Right, Below] | [Below, Right], Self::Regular) => "┘",
1338            ([Right, Below] | [Below, Right], Self::Thick | Self::ThickDashed) => "┛",
1339            ([Right, Below] | [Below, Right], Self::Double) => "╝",
1340            ([Right, Below] | [Below, Right], Self::Rounded) => "╯",
1341            ([Right | Left, Below] | [Below, Right | Left], Self::Halved) => "▀",
1342            ([Right, Below] | [Below, Right], Self::ThinBlock) => "🭿",
1343            ([Below, Left] | [Left, Below], Self::Regular) => "└",
1344            ([Below, Left] | [Left, Below], Self::Thick | Self::ThickDashed) => "┗",
1345            ([Below, Left] | [Left, Below], Self::Double) => "╚",
1346            ([Below, Left] | [Left, Below], Self::Rounded) => "╰",
1347            ([Below, Left] | [Left, Below], Self::ThinBlock) => "🭼",
1348            (sides, Self::Custom { corners, .. }) => match sides {
1349                [Above, Right] | [Right, Above] => corners[0].encode_utf8(&mut char_str),
1350                [Above, Left] | [Left, Above] => corners[1].encode_utf8(&mut char_str),
1351                [Right, Below] | [Below, Right] => corners[2].encode_utf8(&mut char_str),
1352                [Below, Left] | [Left, Below] => corners[3].encode_utf8(&mut char_str),
1353                _ => return,
1354            },
1355            (_, Self::Ascii) => "+",
1356            _ => return,
1357        };
1358
1359        queue!(
1360            stdout,
1361            MoveTo(coord.x as u16, coord.y as u16),
1362            SetStyle(style)
1363        )
1364        .unwrap();
1365        stdout.write_all(char.as_bytes()).unwrap();
1366    }
1367}
1368
1369fn get_cons(
1370    [width, height]: [Option<(f32, bool)>; 2],
1371    child: &Rect,
1372    is_hidden: bool,
1373    is_spawned: bool,
1374    parent: Option<&Rect>,
1375) -> [Option<Constraint>; 2] {
1376    if is_hidden {
1377        let hor_con = child.len(Axis::Horizontal) | EQ(HIDDEN_PRIO) | 0.0;
1378        let ver_con = child.len(Axis::Vertical) | EQ(HIDDEN_PRIO) | 0.0;
1379        if let Some(parent) = parent {
1380            if parent.aligns_with(Axis::Horizontal) {
1381                [
1382                    Some(hor_con),
1383                    height.map(|(h, _)| child.len(Axis::Vertical) | EQ(LEN_PRIO) | h),
1384                ]
1385            } else {
1386                [
1387                    width.map(|(w, _)| child.len(Axis::Horizontal) | EQ(LEN_PRIO) | w),
1388                    Some(ver_con),
1389                ]
1390            }
1391        } else {
1392            [Some(hor_con), Some(ver_con)]
1393        }
1394    } else {
1395        [(width, Axis::Horizontal), (height, Axis::Vertical)].map(|(constraint, axis)| {
1396            let (len, is_manual) = constraint?;
1397            let strength = match (is_spawned, is_manual) {
1398                (true, _) => CONS_SPAWN_LEN_PRIO,
1399                (false, true) => MANUAL_LEN_PRIO,
1400                (false, false) => LEN_PRIO,
1401            };
1402            Some(child.len(axis) | EQ(strength) | len)
1403        })
1404    }
1405}
1406
1407/// Removes every [`Rect`] that's considered a "dependant" of this one
1408///
1409/// Returns the list of [`AreaId`]s that were removed by this action.
1410///
1411/// A `Rect` is considered dependant if it is a child of the removed
1412/// `Rect` or if it was spawned on it. This functions recurses until
1413/// all dependants of all removed `Rect`s are removed.
1414fn remove_dependents(
1415    rect: &mut Rect,
1416    spawned: &mut Vec<(SpawnInfo, Rect)>,
1417    p: &Printer,
1418    mut rm_list: Vec<AreaId>,
1419) -> Vec<AreaId> {
1420    rm_list.push(rect.id());
1421
1422    let vars = {
1423        let [tl, br] = rect.var_points();
1424        [tl.x(), tl.y(), br.x(), br.y()]
1425    };
1426
1427    let rm_spawned: Vec<(SpawnInfo, Rect)> = spawned
1428        .extract_if(.., |(info, _)| {
1429            let Some((_, tl, br)) = p.get_spawn_info(info.id) else {
1430                return false;
1431            };
1432
1433            if tl
1434                .iter()
1435                .chain(br.iter())
1436                .any(|expr| expr.terms.iter().any(|term| vars.contains(&term.variable)))
1437            {
1438                p.remove_spawn_info(info.id);
1439                true
1440            } else {
1441                false
1442            }
1443        })
1444        .collect();
1445
1446    for (mut info, mut rect) in rm_spawned {
1447        p.remove_rect(&mut rect);
1448        p.remove_eqs(info.cons.drain());
1449
1450        rm_list = remove_dependents(&mut rect, spawned, p, rm_list);
1451    }
1452
1453    for (rect, cons) in rect.children_mut().into_iter().flat_map(|c| c.iter_mut()) {
1454        p.remove_rect(rect);
1455        p.remove_eqs(cons.drain());
1456
1457        rm_list = remove_dependents(rect, spawned, p, rm_list);
1458    }
1459
1460    rm_list
1461}
1462
1463/// Sets a [`Rect`], as well as all of its children, to be hidden or
1464/// revealed
1465fn recurse_set_hidden(layout: &mut Layout, id: AreaId, hidden: bool) {
1466    let Some(rect) = layout.get(id) else { return };
1467
1468    if layout.get_parent(id).is_none() {
1469        let [tl, _] = rect.var_points();
1470        for i in 0..layout.spawned.len() {
1471            let (info, rect) = &layout.spawned[i];
1472
1473            if let Some((_, [tl_x, _], _)) = layout.printer.get_spawn_info(info.id)
1474                && tl_x.terms.iter().any(|term| term.variable == tl.x())
1475            {
1476                recurse_set_hidden(layout, rect.id(), hidden);
1477            }
1478        }
1479    }
1480
1481    let Some(rect) = layout.get(id) else { return };
1482
1483    if let Some(children) = rect.children() {
1484        let children: Vec<_> = children.iter().map(|(rect, _)| rect.id()).collect();
1485        for child_id in children {
1486            recurse_set_hidden(layout, child_id, hidden);
1487        }
1488    }
1489
1490    if hidden {
1491        layout.printer.clear_spawn(id);
1492    }
1493    layout.set_constraints(id, None, None, Some(hidden));
1494}
1495
1496static DEFAULT_FRAME_STYLE: Mutex<FrameStyle> = Mutex::new(FrameStyle::Regular);
1497
1498/// Sets the default [`FrameStyle`] for all spawned [`Area`]s
1499///
1500/// By default, it is [`FrameStyle::Regular`], which uses characters
1501/// like `─`, `│` and `┐`.
1502///
1503/// [`Area`]: crate::Area
1504pub fn set_default_frame_style(frame_style: FrameStyle) {
1505    *DEFAULT_FRAME_STYLE.lock().unwrap() = frame_style;
1506}