1#[derive(Debug, Clone, Copy)]
5pub struct Constraint {
6 pub percentage: u16,
8}
9
10impl Constraint {
11 pub fn percentage(percentage: u16) -> Self {
13 Self { percentage }
14 }
15
16 pub fn fixed(size: u16) -> Self {
18 Self { percentage: size }
19 }
20
21 pub fn min(size: u16) -> Self {
23 Self { percentage: size }
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Direction {
30 Horizontal,
32 Vertical,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct Rect {
39 pub x: u16,
41 pub y: u16,
43 pub width: u16,
45 pub height: u16,
47}
48
49impl Rect {
50 pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
52 Self {
53 x,
54 y,
55 width,
56 height,
57 }
58 }
59
60 pub const fn right(&self) -> u16 {
62 self.x.saturating_add(self.width)
63 }
64
65 pub const fn bottom(&self) -> u16 {
67 self.y.saturating_add(self.height)
68 }
69
70 pub const fn is_empty(&self) -> bool {
72 self.width == 0 || self.height == 0
73 }
74}
75
76pub struct Layout {
78 pub width: u16,
80 pub height: u16,
82}
83
84impl Layout {
85 pub fn new(width: u16, height: u16) -> Self {
87 Self { width, height }
88 }
89
90 pub fn is_valid(&self) -> bool {
92 self.width >= 80 && self.height >= 24
93 }
94
95 pub fn content_area(&self) -> Rect {
97 Rect::new(0, 0, self.width, self.height.saturating_sub(3))
98 }
99
100 pub fn input_area(&self) -> Rect {
102 let input_height = 3;
103 let y = self.height.saturating_sub(input_height);
104 Rect::new(0, y, self.width, input_height)
105 }
106
107 pub fn split_vertical(&self, rect: Rect, constraints: &[Constraint]) -> Vec<Rect> {
109 if constraints.is_empty() {
110 return vec![rect];
111 }
112
113 let mut rects = Vec::new();
114 let mut y = rect.y;
115
116 for constraint in constraints {
117 let height = (rect.height as u32 * constraint.percentage as u32 / 100) as u16;
118 rects.push(Rect::new(rect.x, y, rect.width, height));
119 y = y.saturating_add(height);
120 }
121
122 rects
123 }
124
125 pub fn split_horizontal(&self, rect: Rect, constraints: &[Constraint]) -> Vec<Rect> {
127 if constraints.is_empty() {
128 return vec![rect];
129 }
130
131 let mut rects = Vec::new();
132 let mut x = rect.x;
133
134 for constraint in constraints {
135 let width = (rect.width as u32 * constraint.percentage as u32 / 100) as u16;
136 rects.push(Rect::new(x, rect.y, width, rect.height));
137 x = x.saturating_add(width);
138 }
139
140 rects
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_rect_creation() {
150 let rect = Rect::new(0, 0, 80, 24);
151 assert_eq!(rect.x, 0);
152 assert_eq!(rect.y, 0);
153 assert_eq!(rect.width, 80);
154 assert_eq!(rect.height, 24);
155 }
156
157 #[test]
158 fn test_rect_edges() {
159 let rect = Rect::new(10, 5, 20, 15);
160 assert_eq!(rect.right(), 30);
161 assert_eq!(rect.bottom(), 20);
162 }
163
164 #[test]
165 fn test_layout_valid() {
166 let layout = Layout::new(80, 24);
167 assert!(layout.is_valid());
168
169 let layout = Layout::new(79, 24);
170 assert!(!layout.is_valid());
171
172 let layout = Layout::new(80, 23);
173 assert!(!layout.is_valid());
174 }
175
176 #[test]
177 fn test_layout_areas() {
178 let layout = Layout::new(80, 24);
179 let content = layout.content_area();
180 assert_eq!(content.height, 21);
181
182 let input = layout.input_area();
183 assert_eq!(input.height, 3);
184 assert_eq!(input.y, 21);
185 }
186
187 #[test]
188 fn test_split_vertical() {
189 let layout = Layout::new(80, 24);
190 let rect = Rect::new(0, 0, 80, 20);
191 let constraints = vec![Constraint::percentage(50), Constraint::percentage(50)];
192 let rects = layout.split_vertical(rect, &constraints);
193
194 assert_eq!(rects.len(), 2);
195 assert_eq!(rects[0].height, 10);
196 assert_eq!(rects[1].height, 10);
197 }
198
199 #[test]
200 fn test_split_horizontal() {
201 let layout = Layout::new(80, 24);
202 let rect = Rect::new(0, 0, 80, 20);
203 let constraints = vec![Constraint::percentage(30), Constraint::percentage(70)];
204 let rects = layout.split_horizontal(rect, &constraints);
205
206 assert_eq!(rects.len(), 2);
207 assert_eq!(rects[0].width, 24);
208 assert_eq!(rects[1].width, 56);
209 }
210}