1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Constraint {
8 Length(u16),
10 Ratio(u32, u32),
12 Min(u16),
14 Max(u16),
16 Fill,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Direction {
23 Vertical,
25 Horizontal,
27}
28
29pub struct Layout {
31 direction: Direction,
32 constraints: Vec<Constraint>,
33}
34
35impl Layout {
36 pub fn vertical(constraints: Vec<Constraint>) -> Self {
38 Self { direction: Direction::Vertical, constraints }
39 }
40
41 pub fn horizontal(constraints: Vec<Constraint>) -> Self {
43 Self { direction: Direction::Horizontal, constraints }
44 }
45
46 pub fn split(&self, area: Rect) -> Vec<Rect> {
48 match self.direction {
49 Direction::Vertical => self.split_vertical(area),
50 Direction::Horizontal => self.split_horizontal(area),
51 }
52 }
53
54 fn split_vertical(&self, area: Rect) -> Vec<Rect> {
56 let mut result = Vec::new();
57 let mut remaining_height = area.height;
58 let mut y = area.y;
59
60 let mut fixed_length = 0;
62 let mut min_length = 0;
63
64 for constraint in &self.constraints {
65 match constraint {
66 Constraint::Length(len) => fixed_length += len,
67 Constraint::Min(len) => min_length += len,
68 _ => {}
69 }
70 }
71
72 let available_space = remaining_height.saturating_sub(fixed_length);
74
75 let mut ratio_sum = 0;
77 for constraint in &self.constraints {
78 if let Constraint::Ratio(numerator, denominator) = constraint {
79 ratio_sum += numerator * 1000 / denominator;
80 }
81 }
82
83 for constraint in &self.constraints {
85 let height = match constraint {
86 Constraint::Length(len) => *len,
87 Constraint::Min(len) => *len,
88 Constraint::Max(len) => std::cmp::min(*len, remaining_height),
89 Constraint::Ratio(numerator, denominator) => {
90 if ratio_sum > 0 {
91 (available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
92 }
93 else {
94 0
95 }
96 }
97 Constraint::Fill => remaining_height,
98 };
99
100 if height > 0 {
101 result.push(Rect { x: area.x, y, width: area.width, height });
102 y += height;
103 remaining_height -= height;
104 }
105 }
106
107 result
108 }
109
110 fn split_horizontal(&self, area: Rect) -> Vec<Rect> {
112 let mut result = Vec::new();
113 let mut remaining_width = area.width;
114 let mut x = area.x;
115
116 let mut fixed_length = 0;
118 let mut min_length = 0;
119
120 for constraint in &self.constraints {
121 match constraint {
122 Constraint::Length(len) => fixed_length += len,
123 Constraint::Min(len) => min_length += len,
124 _ => {}
125 }
126 }
127
128 let available_space = remaining_width.saturating_sub(fixed_length);
130
131 let mut ratio_sum = 0;
133 for constraint in &self.constraints {
134 if let Constraint::Ratio(numerator, denominator) = constraint {
135 ratio_sum += numerator * 1000 / denominator;
136 }
137 }
138
139 for constraint in &self.constraints {
141 let width = match constraint {
142 Constraint::Length(len) => *len,
143 Constraint::Min(len) => *len,
144 Constraint::Max(len) => std::cmp::min(*len, remaining_width),
145 Constraint::Ratio(numerator, denominator) => {
146 if ratio_sum > 0 {
147 (available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
148 }
149 else {
150 0
151 }
152 }
153 Constraint::Fill => remaining_width,
154 };
155
156 if width > 0 {
157 result.push(Rect { x, y: area.y, width, height: area.height });
158 x += width;
159 remaining_width -= width;
160 }
161 }
162
163 result
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct Rect {
170 pub x: u16,
172 pub y: u16,
174 pub width: u16,
176 pub height: u16,
178}
179
180impl Rect {
181 pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
183 Self { x, y, width, height }
184 }
185
186 pub fn from_size(width: u16, height: u16) -> Self {
188 Self { x: 0, y: 0, width, height }
189 }
190
191 pub fn contains(&self, x: u16, y: u16) -> bool {
193 x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
194 }
195
196 pub fn shrink(&self, margin: u16) -> Self {
198 Self {
199 x: self.x + margin,
200 y: self.y + margin,
201 width: self.width.saturating_sub(2 * margin),
202 height: self.height.saturating_sub(2 * margin),
203 }
204 }
205}