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#[derive(Clone, Default)]
38pub(crate) struct Layouts(Rc<RefCell<InnerLayouts>>);
39
40impl Layouts {
41 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 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 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 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 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 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 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 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 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 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 pub fn reset(&self) {
281 let mut inner = self.0.borrow_mut();
282 inner.list.clear();
283 inner.active_id = None;
284 }
285
286 pub fn remove_window(&self, win: usize) {
288 self.0.borrow_mut().list.remove(win);
289 }
290
291 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 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 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 pub fn get_active_id(&self) -> AreaId {
375 self.0.borrow().active_id.unwrap()
376 }
377
378 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 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 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
424pub struct Layout {
438 main: Rect,
439 spawned: Vec<(SpawnInfo, Rect)>,
440 printer: Arc<Printer>,
441}
442
443impl Layout {
444 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 pub fn main_id(&self) -> AreaId {
454 self.main.id()
455 }
456
457 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 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 fn swap(&mut self, id0: AreaId, id1: AreaId) {
545 self.main.swap(&self.printer, id0, id1);
546 }
547
548 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 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 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 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 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 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 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 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 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 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 pub fn max_value(&self) -> crate::area::Coord {
826 self.printer.max_value()
827 }
828
829 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 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#[derive(Clone)]
870struct SpawnInfo {
871 id: SpawnId,
872 spec: SpawnSpec,
873 cons: Constraints,
874 frame: Frame,
875}
876
877#[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#[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 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 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 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 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 fn is_resizable_on(&self, axis: Axis) -> bool {
989 self.on(axis).is_none()
990 }
991}
992
993#[derive(Default, Clone)]
1003#[allow(clippy::type_complexity)]
1004pub struct Frame {
1005 pub above: bool,
1007 pub below: bool,
1009 pub left: bool,
1011 pub right: bool,
1013 pub style: Option<FrameStyle>,
1015 pub form: Option<FormId>,
1017 #[doc(hidden)]
1023 pub side_texts: [Option<Arc<dyn Fn(usize) -> Text>>; 4],
1024}
1025
1026impl Frame {
1027 pub fn sides(&self) -> impl Iterator<Item = bool> {
1031 [self.above, self.below, self.left, self.right].into_iter()
1032 }
1033
1034 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 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#[derive(Default, Clone, PartialEq, Eq)]
1192pub enum FrameStyle {
1193 #[default]
1195 Regular,
1196 Thick,
1198 Dashed,
1200 ThickDashed,
1202 Double,
1204 Rounded,
1206 Halved,
1208 ThinBlock,
1210 Ascii,
1212 Custom {
1214 sides: [char; 4],
1218 corners: [char; 4],
1223 t_mergers: Option<[char; 4]>,
1243 x_merger: Option<char>,
1246 },
1247}
1248
1249impl FrameStyle {
1250 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
1407fn 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
1463fn 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
1498pub fn set_default_frame_style(frame_style: FrameStyle) {
1505 *DEFAULT_FRAME_STYLE.lock().unwrap() = frame_style;
1506}