1use std::collections::HashMap;
4
5use crate::console::{Console, ConsoleOptions, DynRenderable, Renderable};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Region {
14 pub x: usize,
15 pub y: usize,
16 pub width: usize,
17 pub height: usize,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Direction {
27 Horizontal,
29 Vertical,
31}
32
33#[derive(Debug, Clone)]
39pub enum LayoutNode {
40 Split {
42 direction: Direction,
44 sizes: Vec<usize>,
46 children: Vec<LayoutNode>,
48 },
49 Leaf {
51 name: String,
53 renderable: Option<String>,
55 size: Option<usize>,
57 },
58}
59
60impl LayoutNode {
61 pub fn split(direction: Direction, children: Vec<LayoutNode>) -> Self {
66 let sizes = vec![1; children.len()];
67 Self::Split {
68 direction,
69 sizes,
70 children,
71 }
72 }
73
74 pub fn sizes(mut self, sizes: Vec<usize>) -> Self {
76 if let Self::Split {
77 sizes: ref mut s, ..
78 } = self
79 {
80 *s = sizes;
81 }
82 self
83 }
84}
85
86#[derive(Debug)]
93pub struct Layout {
94 pub root: LayoutNode,
96 pub visible: bool,
98 pub minimum_size: usize,
100 pub renderables: HashMap<String, DynRenderable>,
102 pub splitters: Vec<Box<dyn Splitter>>,
104 next_pane_id: usize,
106}
107
108impl Layout {
109 pub fn new(root: LayoutNode) -> Self {
111 Self {
112 root,
113 visible: true,
114 minimum_size: 1,
115 renderables: HashMap::new(),
116 splitters: Vec::new(),
117 next_pane_id: 0,
118 }
119 }
120
121 pub fn from_renderable(
126 name: impl Into<String>,
127 renderable: impl Renderable + Send + Sync + 'static,
128 ) -> Self {
129 let name = name.into();
130 let node = LayoutNode::Leaf {
131 name: name.clone(),
132 renderable: None,
133 size: None,
134 };
135 let mut renderables = HashMap::new();
136 renderables.insert(name, DynRenderable::new(renderable));
137 Self {
138 root: node,
139 visible: true,
140 minimum_size: 1,
141 renderables,
142 splitters: Vec::new(),
143 next_pane_id: 1,
144 }
145 }
146
147 pub fn split(&mut self, direction: Direction) -> &mut LayoutNode {
153 let name_a = format!("_split_a_{}", self.next_pane_id);
154 let name_b = format!("_split_b_{}", self.next_pane_id + 1);
155 self.next_pane_id += 2;
156
157 let old_root = std::mem::replace(
158 &mut self.root,
159 LayoutNode::Split {
160 direction,
161 sizes: vec![1, 1],
162 children: vec![
163 LayoutNode::Leaf {
164 name: name_a,
165 renderable: None,
166 size: None,
167 },
168 LayoutNode::Leaf {
169 name: name_b,
170 renderable: None,
171 size: None,
172 },
173 ],
174 },
175 );
176
177 if let LayoutNode::Split {
179 ref mut children, ..
180 } = self.root
181 {
182 children[0] = old_root;
183 }
184
185 &mut self.root
186 }
187
188 pub fn unsplit(&mut self) {
191 let replacement = std::mem::replace(
192 &mut self.root,
193 LayoutNode::Leaf {
194 name: String::new(),
195 renderable: None,
196 size: None,
197 },
198 );
199 match replacement {
200 LayoutNode::Split { mut children, .. } if !children.is_empty() => {
201 self.root = children.remove(0);
202 }
203 other => {
204 self.root = other;
205 }
206 }
207 }
208
209 pub fn split_column(&mut self) -> &mut Self {
211 self.split(Direction::Horizontal);
212 self
213 }
214
215 pub fn split_row(&mut self) -> &mut Self {
217 self.split(Direction::Vertical);
218 self
219 }
220
221 pub fn add_split(
229 &mut self,
230 renderable: impl Renderable + Send + Sync + 'static,
231 ratio: usize,
232 ) -> usize {
233 let id = self.next_pane_id;
234 self.next_pane_id += 1;
235 let name = format!("_pane_{}", id);
236 self.renderables
237 .insert(name.clone(), DynRenderable::new(renderable));
238
239 match &mut self.root {
240 LayoutNode::Split {
241 children, sizes, ..
242 } => {
243 children.push(LayoutNode::Leaf {
244 name: name.clone(),
245 renderable: None,
246 size: None,
247 });
248 sizes.push(ratio);
249 children.len() - 1
250 }
251 LayoutNode::Leaf { .. } => {
252 let old_root = std::mem::replace(
254 &mut self.root,
255 LayoutNode::Split {
256 direction: Direction::Vertical,
257 sizes: vec![1, ratio],
258 children: vec![
259 LayoutNode::Leaf {
260 name: String::new(),
261 renderable: None,
262 size: None,
263 },
264 LayoutNode::Leaf {
265 name: name.clone(),
266 renderable: None,
267 size: None,
268 },
269 ],
270 },
271 );
272 if let LayoutNode::Split {
273 ref mut children, ..
274 } = self.root
275 {
276 children[0] = old_root;
277 }
278 1
279 }
280 }
281 }
282
283 pub fn renderable(&self) -> Option<&dyn Renderable> {
285 match &self.root {
286 LayoutNode::Leaf { name, .. } => {
287 self.renderables.get(name).map(|dr| dr as &dyn Renderable)
288 }
289 _ => None,
290 }
291 }
292
293 pub fn children(&self) -> &[LayoutNode] {
295 match &self.root {
296 LayoutNode::Split { children, .. } => children,
297 _ => &[],
298 }
299 }
300
301 pub fn splitters(&self) -> Vec<&dyn Splitter> {
303 self.splitters.iter().map(|s| s.as_ref()).collect()
304 }
305
306 pub fn tree(&self) -> &LayoutNode {
308 &self.root
309 }
310
311 pub fn map(&mut self, f: impl Fn(&dyn Renderable) -> DynRenderable) {
314 let mut new_renderables = HashMap::new();
315 for (name, dr) in &self.renderables {
316 let new_dr = f(dr as &dyn Renderable);
317 new_renderables.insert(name.clone(), new_dr);
318 }
319 self.renderables = new_renderables;
320 }
321
322 pub fn get(&self, name: &str) -> Option<&dyn Renderable> {
324 self.renderables.get(name).map(|dr| dr as &dyn Renderable)
325 }
326
327 pub fn update(
329 &mut self,
330 name: &str,
331 renderable: impl Renderable + Send + Sync + 'static,
332 ) -> bool {
333 if self.renderables.contains_key(name) {
334 self.renderables
335 .insert(name.to_string(), DynRenderable::new(renderable));
336 true
337 } else {
338 false
339 }
340 }
341
342 pub fn refresh_screen(&mut self, console: &mut Console) {
347 if !self.visible {
348 return;
349 }
350 let dims = crate::console::ConsoleDimensions::detect();
351 let regions = self.compute(dims.width, dims.height);
352 for (name, _region) in ®ions {
354 if let Some(renderable) = self.renderables.get(name) {
355 let rendered = renderable.render(&ConsoleOptions::default());
358 let text = rendered.to_ansi();
359 if !text.is_empty() {
360 console.print_str(&text);
361 }
362 }
363 }
364 }
365
366 pub fn compute(&self, total_width: usize, total_height: usize) -> Vec<(String, Region)> {
371 let mut regions = Vec::new();
372 let region = Region {
373 x: 0,
374 y: 0,
375 width: total_width,
376 height: total_height,
377 };
378 Self::layout_node(&self.root, region, &mut regions);
379 regions
380 }
381
382 fn layout_node(node: &LayoutNode, region: Region, out: &mut Vec<(String, Region)>) {
383 match node {
384 LayoutNode::Leaf { name, size, .. } => {
385 let mut r = region;
386 if let Some(s) = size {
387 r.width = r.width.min(*s);
388 r.height = r.height.min(*s);
389 } else {
390 r.width = r.width.max(2);
391 r.height = r.height.max(1);
392 }
393 out.push((name.clone(), r));
394 }
395 LayoutNode::Split {
396 direction,
397 sizes,
398 children,
399 } => {
400 let total_size: usize = sizes.iter().sum();
401 if total_size == 0 || children.is_empty() {
402 return;
403 }
404 let count = children.len();
405
406 match direction {
407 Direction::Horizontal => {
408 let mut x = region.x;
409 let total_spacing = count.saturating_sub(1);
410 let avail = region.width.saturating_sub(total_spacing);
411 for (i, child) in children.iter().enumerate() {
412 let ratio = sizes.get(i).copied().unwrap_or(1);
413 let child_w = (avail * ratio) / total_size;
414 let child_r = Region {
415 x,
416 y: region.y,
417 width: child_w.max(1),
418 height: region.height,
419 };
420 Self::layout_node(child, child_r, out);
421 x += child_w + 1; }
423 }
424 Direction::Vertical => {
425 let mut y = region.y;
426 for (i, child) in children.iter().enumerate() {
427 let ratio = sizes.get(i).copied().unwrap_or(1);
428 let child_h = (region.height * ratio) / total_size;
429 let child_r = Region {
430 x: region.x,
431 y,
432 width: region.width,
433 height: child_h.max(1),
434 };
435 Self::layout_node(child, child_r, out);
436 y += child_h;
437 }
438 }
439 }
440 }
441 }
442 }
443}
444
445pub trait Splitter: std::fmt::Debug {
454 fn split(&self, region: &Region, children: &[LayoutNode], direction: &Direction)
458 -> Vec<Region>;
459}
460
461#[derive(Debug)]
463pub struct NoSplitter;
464
465impl Splitter for NoSplitter {
466 fn split(
467 &self,
468 region: &Region,
469 children: &[LayoutNode],
470 direction: &Direction,
471 ) -> Vec<Region> {
472 let count = children.len().max(1);
473 match direction {
474 Direction::Horizontal => {
475 let col_width = region.width / count;
476 children
477 .iter()
478 .enumerate()
479 .map(|(i, _)| Region {
480 x: region.x + i * col_width,
481 y: region.y,
482 width: col_width,
483 height: region.height,
484 })
485 .collect()
486 }
487 Direction::Vertical => {
488 let row_height = region.height / count;
489 children
490 .iter()
491 .enumerate()
492 .map(|(i, _)| Region {
493 x: region.x,
494 y: region.y + i * row_height,
495 width: region.width,
496 height: row_height,
497 })
498 .collect()
499 }
500 }
501 }
502}
503
504#[derive(Debug)]
506pub struct ColumnSplitter;
507
508impl Splitter for ColumnSplitter {
509 fn split(
510 &self,
511 region: &Region,
512 children: &[LayoutNode],
513 _direction: &Direction,
514 ) -> Vec<Region> {
515 let count = children.len().max(1);
516 let col_width = region.width / count;
517 children
518 .iter()
519 .enumerate()
520 .map(|(i, _)| Region {
521 x: region.x + i * col_width,
522 y: region.y,
523 width: col_width,
524 height: region.height,
525 })
526 .collect()
527 }
528}
529
530#[derive(Debug)]
532pub struct RowSplitter;
533
534impl Splitter for RowSplitter {
535 fn split(
536 &self,
537 region: &Region,
538 children: &[LayoutNode],
539 _direction: &Direction,
540 ) -> Vec<Region> {
541 let count = children.len().max(1);
542 let row_height = region.height / count;
543 children
544 .iter()
545 .enumerate()
546 .map(|(i, _)| Region {
547 x: region.x,
548 y: region.y + i * row_height,
549 width: region.width,
550 height: row_height,
551 })
552 .collect()
553 }
554}
555
556#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_region_defaults() {
566 let r = Region {
567 x: 0,
568 y: 0,
569 width: 80,
570 height: 24,
571 };
572 assert_eq!(r.width, 80);
573 assert_eq!(r.height, 24);
574 }
575
576 #[test]
577 fn test_layout_single_leaf() {
578 let node = LayoutNode::Leaf {
579 name: "root".into(),
580 renderable: None,
581 size: None,
582 };
583 let layout = Layout::new(node);
584 let regions = layout.compute(80, 24);
585 assert_eq!(regions.len(), 1);
586 assert_eq!(regions[0].0, "root");
587 }
588
589 #[test]
590 fn test_layout_horizontal_split() {
591 let children = vec![
592 LayoutNode::Leaf {
593 name: "left".into(),
594 renderable: None,
595 size: None,
596 },
597 LayoutNode::Leaf {
598 name: "right".into(),
599 renderable: None,
600 size: None,
601 },
602 ];
603 let node = LayoutNode::split(Direction::Horizontal, children);
604 let layout = Layout::new(node);
605 let regions = layout.compute(80, 24);
606 assert_eq!(regions.len(), 2);
607 assert!(regions[0].1.x < regions[1].1.x);
608 }
609
610 #[test]
611 fn test_layout_vertical_split() {
612 let children = vec![
613 LayoutNode::Leaf {
614 name: "top".into(),
615 renderable: None,
616 size: None,
617 },
618 LayoutNode::Leaf {
619 name: "bottom".into(),
620 renderable: None,
621 size: None,
622 },
623 ];
624 let node = LayoutNode::split(Direction::Vertical, children);
625 let layout = Layout::new(node);
626 let regions = layout.compute(80, 24);
627 assert_eq!(regions.len(), 2);
628 assert!(regions[0].1.y < regions[1].1.y);
629 }
630
631 #[test]
632 fn test_split_method() {
633 let mut layout = Layout::new(LayoutNode::Leaf {
634 name: "root".into(),
635 renderable: None,
636 size: None,
637 });
638 layout.split(Direction::Horizontal);
639 match &layout.root {
641 LayoutNode::Split { children, .. } => {
642 assert_eq!(children.len(), 2);
643 }
644 _ => panic!("expected Split after split()"),
645 }
646 }
647
648 #[test]
649 fn test_unsplit_method() {
650 let mut layout = Layout::new(LayoutNode::Leaf {
651 name: "root".into(),
652 renderable: None,
653 size: None,
654 });
655 layout.split(Direction::Horizontal);
656 layout.unsplit();
657 match &layout.root {
659 LayoutNode::Leaf { .. } => {} _ => panic!("expected Leaf after unsplit()"),
661 }
662 }
663
664 #[test]
665 fn test_split_column() {
666 let mut layout = Layout::new(LayoutNode::Leaf {
667 name: "root".into(),
668 renderable: None,
669 size: None,
670 });
671 layout.split_column();
672 match &layout.root {
673 LayoutNode::Split { direction, .. } => {
674 assert_eq!(*direction, Direction::Horizontal);
675 }
676 _ => panic!("expected Horizontal split"),
677 }
678 }
679
680 #[test]
681 fn test_split_row() {
682 let mut layout = Layout::new(LayoutNode::Leaf {
683 name: "root".into(),
684 renderable: None,
685 size: None,
686 });
687 layout.split_row();
688 match &layout.root {
689 LayoutNode::Split { direction, .. } => {
690 assert_eq!(*direction, Direction::Vertical);
691 }
692 _ => panic!("expected Vertical split"),
693 }
694 }
695
696 #[test]
697 fn test_children_method() {
698 let mut layout = Layout::new(LayoutNode::Leaf {
699 name: "root".into(),
700 renderable: None,
701 size: None,
702 });
703 assert!(layout.children().is_empty());
705 layout.split(Direction::Horizontal);
706 assert_eq!(layout.children().len(), 2);
707 }
708
709 #[test]
710 fn test_tree_method() {
711 let layout = Layout::new(LayoutNode::Leaf {
712 name: "root".into(),
713 renderable: None,
714 size: None,
715 });
716 match layout.tree() {
717 LayoutNode::Leaf { name, .. } => assert_eq!(name, "root"),
718 _ => panic!("expected Leaf"),
719 }
720 }
721
722 #[test]
723 fn test_renderable_none_for_empty_layout() {
724 let layout = Layout::new(LayoutNode::Leaf {
725 name: "root".into(),
726 renderable: None,
727 size: None,
728 });
729 assert!(layout.renderable().is_none());
731 }
732
733 #[test]
734 fn test_from_renderable() {
735 let layout = Layout::from_renderable("main", "hello world");
736 assert!(layout.get("main").is_some());
737 }
738
739 #[test]
740 fn test_get_and_update() {
741 let mut layout = Layout::from_renderable("main", "initial");
742 assert!(layout.get("main").is_some());
743
744 let updated = layout.update("main", "updated");
745 assert!(updated);
746
747 let not_found = layout.update("nonexistent", "nope");
749 assert!(!not_found);
750 }
751
752 #[test]
753 fn test_map() {
754 let mut layout = Layout::from_renderable("main", "hello");
755 layout.map(|_r| DynRenderable::new("mapped"));
756 assert!(layout.get("main").is_some());
757 }
758
759 #[test]
760 fn test_add_split_to_leaf() {
761 let mut layout = Layout::from_renderable("main", "content");
762 let id = layout.add_split("second", 2);
763 match &layout.root {
765 LayoutNode::Split {
766 children, sizes, ..
767 } => {
768 assert_eq!(children.len(), 2);
769 assert_eq!(*sizes, vec![1, 2]);
770 assert_eq!(id, 1);
771 }
772 _ => panic!("expected Split after add_split"),
773 }
774 }
775
776 #[test]
777 fn test_no_splitter() {
778 let splitter = NoSplitter;
779 let children = vec![
780 LayoutNode::Leaf {
781 name: "a".into(),
782 renderable: None,
783 size: None,
784 },
785 LayoutNode::Leaf {
786 name: "b".into(),
787 renderable: None,
788 size: None,
789 },
790 ];
791 let region = Region {
792 x: 0,
793 y: 0,
794 width: 80,
795 height: 24,
796 };
797 let regions = splitter.split(®ion, &children, &Direction::Horizontal);
798 assert_eq!(regions.len(), 2);
799 assert_eq!(regions[0].width, 40);
800 assert_eq!(regions[1].width, 40);
801 }
802
803 #[test]
804 fn test_column_splitter() {
805 let splitter = ColumnSplitter;
806 let children = vec![
807 LayoutNode::Leaf {
808 name: "a".into(),
809 renderable: None,
810 size: None,
811 },
812 LayoutNode::Leaf {
813 name: "b".into(),
814 renderable: None,
815 size: None,
816 },
817 LayoutNode::Leaf {
818 name: "c".into(),
819 renderable: None,
820 size: None,
821 },
822 ];
823 let region = Region {
824 x: 0,
825 y: 0,
826 width: 90,
827 height: 24,
828 };
829 let regions = splitter.split(®ion, &children, &Direction::Vertical);
830 assert_eq!(regions.len(), 3);
831 assert_eq!(regions[0].width, 30);
832 assert_eq!(regions[1].x, 30);
833 assert_eq!(regions[2].x, 60);
834 }
835
836 #[test]
837 fn test_row_splitter() {
838 let splitter = RowSplitter;
839 let children = vec![
840 LayoutNode::Leaf {
841 name: "a".into(),
842 renderable: None,
843 size: None,
844 },
845 LayoutNode::Leaf {
846 name: "b".into(),
847 renderable: None,
848 size: None,
849 },
850 ];
851 let region = Region {
852 x: 0,
853 y: 0,
854 width: 80,
855 height: 24,
856 };
857 let regions = splitter.split(®ion, &children, &Direction::Horizontal);
858 assert_eq!(regions.len(), 2);
859 assert_eq!(regions[0].height, 12);
860 assert_eq!(regions[1].y, 12);
861 }
862
863 #[test]
864 fn test_splitters_method() {
865 let layout = Layout::new(LayoutNode::Leaf {
866 name: "root".into(),
867 renderable: None,
868 size: None,
869 });
870 assert!(layout.splitters().is_empty());
871 }
872
873 #[test]
874 fn test_compute_with_fixed_size() {
875 let node = LayoutNode::Leaf {
876 name: "fixed".into(),
877 renderable: None,
878 size: Some(10),
879 };
880 let layout = Layout::new(node);
881 let regions = layout.compute(80, 24);
882 assert_eq!(regions[0].1.width, 10);
883 assert_eq!(regions[0].1.height, 10);
884 }
885}