1use crate::rendering::frame::{Cursor, Frame};
2use crate::rendering::line::Line;
3
4pub struct Layout {
10 sections: Vec<Vec<Line>>,
11 cursor: Option<Cursor>,
12 cursor_section_index: Option<usize>,
13}
14
15impl Layout {
16 pub fn new() -> Self {
17 Self { sections: Vec::new(), cursor: None, cursor_section_index: None }
18 }
19
20 pub fn section(&mut self, lines: Vec<Line>) {
22 self.sections.push(lines);
23 }
24
25 pub fn section_with_cursor(&mut self, lines: Vec<Line>, cursor: Cursor) {
27 self.cursor_section_index = Some(self.sections.len());
28 self.cursor = Some(cursor);
29 self.sections.push(lines);
30 }
31
32 pub fn into_frame(self) -> Frame {
34 let mut all_lines = Vec::new();
35 let mut section_offsets = Vec::with_capacity(self.sections.len());
36
37 for section in &self.sections {
38 section_offsets.push(all_lines.len());
39 all_lines.extend(section.iter().cloned());
40 }
41
42 let cursor = match (self.cursor_section_index, self.cursor) {
43 (Some(idx), Some(c)) => Cursor { row: section_offsets[idx] + c.row, col: c.col, is_visible: c.is_visible },
44 _ => Cursor { row: 0, col: 0, is_visible: false },
45 };
46
47 Frame::new(all_lines).with_cursor(cursor)
48 }
49}
50
51impl Default for Layout {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn empty_layout_produces_empty_frame() {
63 let layout = Layout::new();
64 let frame = layout.into_frame();
65 assert!(frame.lines().is_empty());
66 assert!(!frame.cursor().is_visible);
67 }
68
69 #[test]
70 fn sections_are_stacked_in_order() {
71 let mut layout = Layout::new();
72 layout.section(vec![Line::new("a1"), Line::new("a2")]);
73 layout.section(vec![Line::new("b1")]);
74 let frame = layout.into_frame();
75 assert_eq!(frame.lines().len(), 3);
76 assert_eq!(frame.lines()[0].plain_text(), "a1");
77 assert_eq!(frame.lines()[2].plain_text(), "b1");
78 }
79
80 #[test]
81 fn cursor_offset_is_computed_from_section_position() {
82 let mut layout = Layout::new();
83 layout.section(vec![Line::new("header1"), Line::new("header2")]);
84 layout.section_with_cursor(vec![Line::new("input")], Cursor { row: 0, col: 5, is_visible: true });
85 layout.section(vec![Line::new("footer")]);
86
87 let frame = layout.into_frame();
88 assert_eq!(frame.cursor().row, 2); assert_eq!(frame.cursor().col, 5);
90 assert!(frame.cursor().is_visible);
91 }
92
93 #[test]
94 fn cursor_row_adds_section_offset_and_local_row() {
95 let mut layout = Layout::new();
96 layout.section(vec![Line::new("a")]);
97 layout.section_with_cursor(
98 vec![Line::new("b1"), Line::new("b2"), Line::new("b3")],
99 Cursor { row: 2, col: 3, is_visible: true },
100 );
101
102 let frame = layout.into_frame();
103 assert_eq!(frame.cursor().row, 3); assert_eq!(frame.cursor().col, 3);
105 }
106}