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 { direction, sizes, children }
68 }
69
70 pub fn sizes(mut self, sizes: Vec<usize>) -> Self {
72 if let Self::Split { sizes: ref mut s, .. } = self {
73 *s = sizes;
74 }
75 self
76 }
77}
78
79#[derive(Debug)]
86pub struct Layout {
87 pub root: LayoutNode,
89 pub visible: bool,
91 pub minimum_size: usize,
93 pub renderables: HashMap<String, DynRenderable>,
95 pub splitters: Vec<Box<dyn Splitter>>,
97 next_pane_id: usize,
99}
100
101impl Layout {
102 pub fn new(root: LayoutNode) -> Self {
104 Self {
105 root,
106 visible: true,
107 minimum_size: 1,
108 renderables: HashMap::new(),
109 splitters: Vec::new(),
110 next_pane_id: 0,
111 }
112 }
113
114 pub fn from_renderable(
119 name: impl Into<String>,
120 renderable: impl Renderable + Send + Sync + 'static,
121 ) -> Self {
122 let name = name.into();
123 let node = LayoutNode::Leaf {
124 name: name.clone(),
125 renderable: None,
126 size: None,
127 };
128 let mut renderables = HashMap::new();
129 renderables.insert(name, DynRenderable::new(renderable));
130 Self {
131 root: node,
132 visible: true,
133 minimum_size: 1,
134 renderables,
135 splitters: Vec::new(),
136 next_pane_id: 1,
137 }
138 }
139
140 pub fn split(&mut self, direction: Direction) -> &mut LayoutNode {
146 let name_a = format!("_split_a_{}", self.next_pane_id);
147 let name_b = format!("_split_b_{}", self.next_pane_id + 1);
148 self.next_pane_id += 2;
149
150 let old_root = std::mem::replace(
151 &mut self.root,
152 LayoutNode::Split {
153 direction,
154 sizes: vec![1, 1],
155 children: vec![
156 LayoutNode::Leaf { name: name_a, renderable: None, size: None },
157 LayoutNode::Leaf { name: name_b, renderable: None, size: None },
158 ],
159 },
160 );
161
162 if let LayoutNode::Split { ref mut children, .. } = self.root {
164 children[0] = old_root;
165 }
166
167 &mut self.root
168 }
169
170 pub fn unsplit(&mut self) {
173 let replacement = std::mem::replace(
174 &mut self.root,
175 LayoutNode::Leaf { name: String::new(), renderable: None, size: None },
176 );
177 match replacement {
178 LayoutNode::Split { mut children, .. } if !children.is_empty() => {
179 self.root = children.remove(0);
180 }
181 other => {
182 self.root = other;
183 }
184 }
185 }
186
187 pub fn split_column(&mut self) -> &mut Self {
189 self.split(Direction::Horizontal);
190 self
191 }
192
193 pub fn split_row(&mut self) -> &mut Self {
195 self.split(Direction::Vertical);
196 self
197 }
198
199 pub fn add_split(
207 &mut self,
208 renderable: impl Renderable + Send + Sync + 'static,
209 ratio: usize,
210 ) -> usize {
211 let id = self.next_pane_id;
212 self.next_pane_id += 1;
213 let name = format!("_pane_{}", id);
214 self.renderables.insert(name.clone(), DynRenderable::new(renderable));
215
216 match &mut self.root {
217 LayoutNode::Split { children, sizes, .. } => {
218 children.push(LayoutNode::Leaf {
219 name: name.clone(),
220 renderable: None,
221 size: None,
222 });
223 sizes.push(ratio);
224 children.len() - 1
225 }
226 LayoutNode::Leaf { .. } => {
227 let old_root = std::mem::replace(
229 &mut self.root,
230 LayoutNode::Split {
231 direction: Direction::Vertical,
232 sizes: vec![1, ratio],
233 children: vec![
234 LayoutNode::Leaf {
235 name: String::new(),
236 renderable: None,
237 size: None,
238 },
239 LayoutNode::Leaf {
240 name: name.clone(),
241 renderable: None,
242 size: None,
243 },
244 ],
245 },
246 );
247 if let LayoutNode::Split { ref mut children, .. } = self.root {
248 children[0] = old_root;
249 }
250 1
251 }
252 }
253 }
254
255 pub fn renderable(&self) -> Option<&dyn Renderable> {
257 match &self.root {
258 LayoutNode::Leaf { name, .. } => {
259 self.renderables.get(name).map(|dr| dr as &dyn Renderable)
260 }
261 _ => None,
262 }
263 }
264
265 pub fn children(&self) -> &[LayoutNode] {
267 match &self.root {
268 LayoutNode::Split { children, .. } => children,
269 _ => &[],
270 }
271 }
272
273 pub fn splitters(&self) -> Vec<&dyn Splitter> {
275 self.splitters.iter().map(|s| s.as_ref()).collect()
276 }
277
278 pub fn tree(&self) -> &LayoutNode {
280 &self.root
281 }
282
283 pub fn map(&mut self, f: impl Fn(&dyn Renderable) -> DynRenderable) {
286 let mut new_renderables = HashMap::new();
287 for (name, dr) in &self.renderables {
288 let new_dr = f(dr as &dyn Renderable);
289 new_renderables.insert(name.clone(), new_dr);
290 }
291 self.renderables = new_renderables;
292 }
293
294 pub fn get(&self, name: &str) -> Option<&dyn Renderable> {
296 self.renderables.get(name).map(|dr| dr as &dyn Renderable)
297 }
298
299 pub fn update(
301 &mut self,
302 name: &str,
303 renderable: impl Renderable + Send + Sync + 'static,
304 ) -> bool {
305 if self.renderables.contains_key(name) {
306 self.renderables.insert(name.to_string(), DynRenderable::new(renderable));
307 true
308 } else {
309 false
310 }
311 }
312
313 pub fn refresh_screen(&mut self, console: &mut Console) {
318 if !self.visible {
319 return;
320 }
321 let dims = crate::console::ConsoleDimensions::detect();
322 let regions = self.compute(dims.width, dims.height);
323 for (name, _region) in ®ions {
325 if let Some(renderable) = self.renderables.get(name) {
326 let rendered = renderable.render(&ConsoleOptions::default());
329 let text = rendered.to_ansi();
330 if !text.is_empty() {
331 let _ = console.print_str(&text);
332 }
333 }
334 }
335 }
336
337 pub fn compute(&self, total_width: usize, total_height: usize) -> Vec<(String, Region)> {
342 let mut regions = Vec::new();
343 let region = Region { x: 0, y: 0, width: total_width, height: total_height };
344 Self::layout_node(&self.root, region, &mut regions);
345 regions
346 }
347
348 fn layout_node(node: &LayoutNode, region: Region, out: &mut Vec<(String, Region)>) {
349 match node {
350 LayoutNode::Leaf { name, size, .. } => {
351 let mut r = region;
352 if let Some(s) = size {
353 r.width = r.width.min(*s);
354 r.height = r.height.min(*s);
355 } else {
356 r.width = r.width.max(2);
357 r.height = r.height.max(1);
358 }
359 out.push((name.clone(), r));
360 }
361 LayoutNode::Split { direction, sizes, children } => {
362 let total_size: usize = sizes.iter().sum();
363 if total_size == 0 || children.is_empty() {
364 return;
365 }
366 let count = children.len();
367
368 match direction {
369 Direction::Horizontal => {
370 let mut x = region.x;
371 let total_spacing = count.saturating_sub(1);
372 let avail = region.width.saturating_sub(total_spacing);
373 for (i, child) in children.iter().enumerate() {
374 let ratio = sizes.get(i).copied().unwrap_or(1);
375 let child_w = (avail * ratio) / total_size;
376 let child_r = Region {
377 x,
378 y: region.y,
379 width: child_w.max(1),
380 height: region.height,
381 };
382 Self::layout_node(child, child_r, out);
383 x += child_w + 1; }
385 }
386 Direction::Vertical => {
387 let mut y = region.y;
388 for (i, child) in children.iter().enumerate() {
389 let ratio = sizes.get(i).copied().unwrap_or(1);
390 let child_h = (region.height * ratio) / total_size;
391 let child_r = Region {
392 x: region.x,
393 y,
394 width: region.width,
395 height: child_h.max(1),
396 };
397 Self::layout_node(child, child_r, out);
398 y += child_h;
399 }
400 }
401 }
402 }
403 }
404 }
405}
406
407pub trait Splitter: std::fmt::Debug {
416 fn split(&self, region: &Region, children: &[LayoutNode], direction: &Direction) -> Vec<Region>;
420}
421
422#[derive(Debug)]
424pub struct NoSplitter;
425
426impl Splitter for NoSplitter {
427 fn split(&self, region: &Region, children: &[LayoutNode], direction: &Direction) -> Vec<Region> {
428 let count = children.len().max(1);
429 match direction {
430 Direction::Horizontal => {
431 let col_width = region.width / count;
432 children
433 .iter()
434 .enumerate()
435 .map(|(i, _)| Region {
436 x: region.x + i * col_width,
437 y: region.y,
438 width: col_width,
439 height: region.height,
440 })
441 .collect()
442 }
443 Direction::Vertical => {
444 let row_height = region.height / count;
445 children
446 .iter()
447 .enumerate()
448 .map(|(i, _)| Region {
449 x: region.x,
450 y: region.y + i * row_height,
451 width: region.width,
452 height: row_height,
453 })
454 .collect()
455 }
456 }
457 }
458}
459
460#[derive(Debug)]
462pub struct ColumnSplitter;
463
464impl Splitter for ColumnSplitter {
465 fn split(&self, region: &Region, children: &[LayoutNode], _direction: &Direction) -> Vec<Region> {
466 let count = children.len().max(1);
467 let col_width = region.width / count;
468 children
469 .iter()
470 .enumerate()
471 .map(|(i, _)| Region {
472 x: region.x + i * col_width,
473 y: region.y,
474 width: col_width,
475 height: region.height,
476 })
477 .collect()
478 }
479}
480
481#[derive(Debug)]
483pub struct RowSplitter;
484
485impl Splitter for RowSplitter {
486 fn split(&self, region: &Region, children: &[LayoutNode], _direction: &Direction) -> Vec<Region> {
487 let count = children.len().max(1);
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#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_region_defaults() {
512 let r = Region { x: 0, y: 0, width: 80, height: 24 };
513 assert_eq!(r.width, 80);
514 assert_eq!(r.height, 24);
515 }
516
517 #[test]
518 fn test_layout_single_leaf() {
519 let node = LayoutNode::Leaf {
520 name: "root".into(),
521 renderable: None,
522 size: None,
523 };
524 let layout = Layout::new(node);
525 let regions = layout.compute(80, 24);
526 assert_eq!(regions.len(), 1);
527 assert_eq!(regions[0].0, "root");
528 }
529
530 #[test]
531 fn test_layout_horizontal_split() {
532 let children = vec![
533 LayoutNode::Leaf { name: "left".into(), renderable: None, size: None },
534 LayoutNode::Leaf { name: "right".into(), renderable: None, size: None },
535 ];
536 let node = LayoutNode::split(Direction::Horizontal, children);
537 let layout = Layout::new(node);
538 let regions = layout.compute(80, 24);
539 assert_eq!(regions.len(), 2);
540 assert!(regions[0].1.x < regions[1].1.x);
541 }
542
543 #[test]
544 fn test_layout_vertical_split() {
545 let children = vec![
546 LayoutNode::Leaf { name: "top".into(), renderable: None, size: None },
547 LayoutNode::Leaf { name: "bottom".into(), renderable: None, size: None },
548 ];
549 let node = LayoutNode::split(Direction::Vertical, children);
550 let layout = Layout::new(node);
551 let regions = layout.compute(80, 24);
552 assert_eq!(regions.len(), 2);
553 assert!(regions[0].1.y < regions[1].1.y);
554 }
555
556 #[test]
557 fn test_split_method() {
558 let mut layout = Layout::new(LayoutNode::Leaf {
559 name: "root".into(),
560 renderable: None,
561 size: None,
562 });
563 layout.split(Direction::Horizontal);
564 match &layout.root {
566 LayoutNode::Split { children, .. } => {
567 assert_eq!(children.len(), 2);
568 }
569 _ => panic!("expected Split after split()"),
570 }
571 }
572
573 #[test]
574 fn test_unsplit_method() {
575 let mut layout = Layout::new(LayoutNode::Leaf {
576 name: "root".into(),
577 renderable: None,
578 size: None,
579 });
580 layout.split(Direction::Horizontal);
581 layout.unsplit();
582 match &layout.root {
584 LayoutNode::Leaf { .. } => {} _ => panic!("expected Leaf after unsplit()"),
586 }
587 }
588
589 #[test]
590 fn test_split_column() {
591 let mut layout = Layout::new(LayoutNode::Leaf {
592 name: "root".into(),
593 renderable: None,
594 size: None,
595 });
596 layout.split_column();
597 match &layout.root {
598 LayoutNode::Split { direction, .. } => {
599 assert_eq!(*direction, Direction::Horizontal);
600 }
601 _ => panic!("expected Horizontal split"),
602 }
603 }
604
605 #[test]
606 fn test_split_row() {
607 let mut layout = Layout::new(LayoutNode::Leaf {
608 name: "root".into(),
609 renderable: None,
610 size: None,
611 });
612 layout.split_row();
613 match &layout.root {
614 LayoutNode::Split { direction, .. } => {
615 assert_eq!(*direction, Direction::Vertical);
616 }
617 _ => panic!("expected Vertical split"),
618 }
619 }
620
621 #[test]
622 fn test_children_method() {
623 let mut layout = Layout::new(LayoutNode::Leaf {
624 name: "root".into(),
625 renderable: None,
626 size: None,
627 });
628 assert!(layout.children().is_empty());
630 layout.split(Direction::Horizontal);
631 assert_eq!(layout.children().len(), 2);
632 }
633
634 #[test]
635 fn test_tree_method() {
636 let layout = Layout::new(LayoutNode::Leaf {
637 name: "root".into(),
638 renderable: None,
639 size: None,
640 });
641 match layout.tree() {
642 LayoutNode::Leaf { name, .. } => assert_eq!(name, "root"),
643 _ => panic!("expected Leaf"),
644 }
645 }
646
647 #[test]
648 fn test_renderable_none_for_empty_layout() {
649 let layout = Layout::new(LayoutNode::Leaf {
650 name: "root".into(),
651 renderable: None,
652 size: None,
653 });
654 assert!(layout.renderable().is_none());
656 }
657
658 #[test]
659 fn test_from_renderable() {
660 let layout = Layout::from_renderable("main", "hello world");
661 assert!(layout.get("main").is_some());
662 }
663
664 #[test]
665 fn test_get_and_update() {
666 let mut layout = Layout::from_renderable("main", "initial");
667 assert!(layout.get("main").is_some());
668
669 let updated = layout.update("main", "updated");
670 assert!(updated);
671
672 let not_found = layout.update("nonexistent", "nope");
674 assert!(!not_found);
675 }
676
677 #[test]
678 fn test_map() {
679 let mut layout = Layout::from_renderable("main", "hello");
680 layout.map(|_r| DynRenderable::new("mapped"));
681 assert!(layout.get("main").is_some());
682 }
683
684 #[test]
685 fn test_add_split_to_leaf() {
686 let mut layout = Layout::from_renderable("main", "content");
687 let id = layout.add_split("second", 2);
688 match &layout.root {
690 LayoutNode::Split { children, sizes, .. } => {
691 assert_eq!(children.len(), 2);
692 assert_eq!(*sizes, vec![1, 2]);
693 assert_eq!(id, 1);
694 }
695 _ => panic!("expected Split after add_split"),
696 }
697 }
698
699 #[test]
700 fn test_no_splitter() {
701 let splitter = NoSplitter;
702 let children = vec![
703 LayoutNode::Leaf { name: "a".into(), renderable: None, size: None },
704 LayoutNode::Leaf { name: "b".into(), renderable: None, size: None },
705 ];
706 let region = Region { x: 0, y: 0, width: 80, height: 24 };
707 let regions = splitter.split(®ion, &children, &Direction::Horizontal);
708 assert_eq!(regions.len(), 2);
709 assert_eq!(regions[0].width, 40);
710 assert_eq!(regions[1].width, 40);
711 }
712
713 #[test]
714 fn test_column_splitter() {
715 let splitter = ColumnSplitter;
716 let children = vec![
717 LayoutNode::Leaf { name: "a".into(), renderable: None, size: None },
718 LayoutNode::Leaf { name: "b".into(), renderable: None, size: None },
719 LayoutNode::Leaf { name: "c".into(), renderable: None, size: None },
720 ];
721 let region = Region { x: 0, y: 0, width: 90, height: 24 };
722 let regions = splitter.split(®ion, &children, &Direction::Vertical);
723 assert_eq!(regions.len(), 3);
724 assert_eq!(regions[0].width, 30);
725 assert_eq!(regions[1].x, 30);
726 assert_eq!(regions[2].x, 60);
727 }
728
729 #[test]
730 fn test_row_splitter() {
731 let splitter = RowSplitter;
732 let children = vec![
733 LayoutNode::Leaf { name: "a".into(), renderable: None, size: None },
734 LayoutNode::Leaf { name: "b".into(), renderable: None, size: None },
735 ];
736 let region = Region { x: 0, y: 0, width: 80, height: 24 };
737 let regions = splitter.split(®ion, &children, &Direction::Horizontal);
738 assert_eq!(regions.len(), 2);
739 assert_eq!(regions[0].height, 12);
740 assert_eq!(regions[1].y, 12);
741 }
742
743 #[test]
744 fn test_splitters_method() {
745 let layout = Layout::new(LayoutNode::Leaf {
746 name: "root".into(),
747 renderable: None,
748 size: None,
749 });
750 assert!(layout.splitters().is_empty());
751 }
752
753 #[test]
754 fn test_compute_with_fixed_size() {
755 let node = LayoutNode::Leaf {
756 name: "fixed".into(),
757 renderable: None,
758 size: Some(10),
759 };
760 let layout = Layout::new(node);
761 let regions = layout.compute(80, 24);
762 assert_eq!(regions[0].1.width, 10);
763 assert_eq!(regions[0].1.height, 10);
764 }
765}