1use std::hash::{
2 Hash,
3 Hasher,
4};
5
6use kasuari::{
7 Solver,
8 Variable,
9 WeightedRelation::*,
10};
11
12use super::{
13 Constraint,
14 Direction,
15 Flex,
16 Margin,
17 Rect,
18 Spacing,
19 strengths,
20};
21
22const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Layout {
27 direction: Direction,
28 constraints: Vec<Constraint>,
29 margin: Margin,
30 flex: Flex,
31 spacing: Spacing,
32}
33
34impl Layout {
35 pub fn new<I>(direction: Direction, constraints: I) -> Self
37 where
38 I: IntoIterator,
39 I::Item: Into<Constraint>, {
40 Self {
41 direction,
42 constraints: constraints.into_iter().map(Into::into).collect(),
43 margin: Margin::new(0, 0),
44 flex: Flex::default(),
45 spacing: Spacing::default(),
46 }
47 }
48
49 pub fn vertical<I>(constraints: I) -> Self
51 where
52 I: IntoIterator,
53 I::Item: Into<Constraint>, {
54 Self::new(Direction::Vertical, constraints)
55 }
56
57 pub fn horizontal<I>(constraints: I) -> Self
59 where
60 I: IntoIterator,
61 I::Item: Into<Constraint>, {
62 Self::new(Direction::Horizontal, constraints)
63 }
64
65 pub fn direction(mut self, direction: Direction) -> Self {
67 self.direction = direction;
68 self
69 }
70
71 pub fn constraints<I>(mut self, constraints: I) -> Self
73 where
74 I: IntoIterator,
75 I::Item: Into<Constraint>, {
76 self.constraints = constraints.into_iter().map(Into::into).collect();
77 self
78 }
79
80 pub fn margin(mut self, margin: u16) -> Self {
82 self.margin = Margin::new(margin, margin);
83 self
84 }
85
86 pub fn horizontal_margin(mut self, margin: u16) -> Self {
88 self.margin.horizontal = margin;
89 self
90 }
91
92 pub fn vertical_margin(mut self, margin: u16) -> Self {
94 self.margin.vertical = margin;
95 self
96 }
97
98 pub fn flex(mut self, flex: Flex) -> Self {
100 self.flex = flex;
101 self
102 }
103
104 pub fn spacing<T: Into<Spacing>>(mut self, spacing: T) -> Self {
106 self.spacing = spacing.into();
107 self
108 }
109}
110
111impl Default for Layout {
112 fn default() -> Self {
113 Self {
114 direction: Direction::default(),
115 constraints: Vec::new(),
116 margin: Margin::default(),
117 flex: Flex::default(),
118 spacing: Spacing::default(),
119 }
120 }
121}
122
123impl Hash for Layout {
124 fn hash<H: Hasher>(&self, state: &mut H) {
125 self.direction.hash(state);
126 self.constraints.hash(state);
127 self.margin.hash(state);
128 self.flex.hash(state);
129 self.spacing.hash(state);
130 }
131}
132
133impl Layout {
134 pub fn split(&self, area: Rect) -> Vec<Rect> {
136 self.try_split(area).unwrap_or_default()
137 }
138
139 pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
143 let rects = self.split(area);
144 let mut iter = rects.into_iter();
145 [(); N].map(|_| match iter.next() {
146 | Some(r) => r,
147 | None => Rect::ZERO,
148 })
149 }
150
151 fn try_split(&self, area: Rect) -> Option<Vec<Rect>> {
152 let inner = area.inner(self.margin);
153 if inner.is_empty() {
154 return Some(vec![Rect::ZERO; self.constraints.len()]);
155 }
156
157 let mut solver = Solver::new();
158 let segment_count = self.constraints.len();
159 let spacer_count = segment_count.saturating_add(1);
160
161 let segment_vars: Vec<Variable> = (0..segment_count).map(|_| Variable::new()).collect();
162 let spacer_vars: Vec<Variable> = (0..spacer_count).map(|_| Variable::new()).collect();
163
164 let total_size = match self.direction {
165 | Direction::Horizontal => inner.width,
166 | Direction::Vertical => inner.height,
167 };
168 let total = (total_size as f64 * FLOAT_PRECISION_MULTIPLIER) as i64;
169
170 for &var in &segment_vars {
172 solver
173 .add_constraint(var | GE(kasuari::Strength::new(strengths::REQUIRED)) | 0.0)
174 .ok()?;
175 }
176
177 let mut sum_expr = kasuari::Expression::from_constant(0.0);
179 for &var in segment_vars.iter().chain(spacer_vars.iter()) {
180 sum_expr += var;
181 }
182 solver
183 .add_constraint(
184 sum_expr | EQ(kasuari::Strength::new(strengths::REQUIRED)) | total as f64,
185 )
186 .ok()?;
187
188 for (i, constraint) in self.constraints.iter().enumerate() {
190 let var = segment_vars[i];
191 match constraint {
192 | Constraint::Length(n) => {
193 let target = *n as f64 * FLOAT_PRECISION_MULTIPLIER;
194 solver
195 .add_constraint(
196 var | EQ(kasuari::Strength::new(strengths::LENGTH_SIZE_EQ)) | target,
197 )
198 .ok()?;
199 },
200 | Constraint::Percentage(p) => {
201 let target = total as f64 * (*p as f64) / 100.0;
202 solver
203 .add_constraint(
204 var | EQ(kasuari::Strength::new(strengths::PERCENTAGE_SIZE_EQ)) |
205 target,
206 )
207 .ok()?;
208 },
209 | Constraint::Ratio(n, d) => {
210 let target = total as f64 * (*n as f64) / (*d as f64);
211 solver
212 .add_constraint(
213 var | EQ(kasuari::Strength::new(strengths::RATIO_SIZE_EQ)) | target,
214 )
215 .ok()?;
216 },
217 | Constraint::Min(m) => {
218 let target = *m as f64 * FLOAT_PRECISION_MULTIPLIER;
219 solver
220 .add_constraint(
221 var | GE(kasuari::Strength::new(strengths::MIN_SIZE_GE)) | target,
222 )
223 .ok()?;
224 },
225 | Constraint::Max(m) => {
226 let target = *m as f64 * FLOAT_PRECISION_MULTIPLIER;
227 solver
228 .add_constraint(
229 var | LE(kasuari::Strength::new(strengths::MAX_SIZE_LE)) | target,
230 )
231 .ok()?;
232 },
233 | Constraint::Fill(_) => {
234 },
236 }
237 }
238
239 let spacing_value = match self.spacing {
241 | Spacing::Space(v) => v as f64 * FLOAT_PRECISION_MULTIPLIER,
242 | Spacing::Overlap(v) => -(v as f64) * FLOAT_PRECISION_MULTIPLIER,
243 };
244
245 match self.flex {
246 | Flex::Legacy => {
247 for &var in &spacer_vars {
249 solver
250 .add_constraint(var | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0)
251 .ok()?;
252 }
253 },
254 | Flex::Start => {
255 if let Some(&first) = spacer_vars.first() {
256 solver
257 .add_constraint(
258 first | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
259 )
260 .ok()?;
261 }
262 for &var in spacer_vars
263 .iter()
264 .skip(1)
265 .take(spacer_count.saturating_sub(2))
266 {
267 solver
268 .add_constraint(
269 var | EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
270 spacing_value,
271 )
272 .ok()?;
273 }
274 if let Some(&last) = spacer_vars.last() {
275 solver
276 .add_constraint(
277 last | GE(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
278 )
279 .ok()?;
280 }
281 },
282 | Flex::End => {
283 if let Some(&last) = spacer_vars.last() {
284 solver
285 .add_constraint(
286 last | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
287 )
288 .ok()?;
289 }
290 for &var in spacer_vars
291 .iter()
292 .skip(1)
293 .take(spacer_count.saturating_sub(2))
294 {
295 solver
296 .add_constraint(
297 var | EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
298 spacing_value,
299 )
300 .ok()?;
301 }
302 if let Some(&first) = spacer_vars.first() {
303 solver
304 .add_constraint(
305 first | GE(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
306 )
307 .ok()?;
308 }
309 },
310 | Flex::Center => {
311 for &var in spacer_vars
312 .iter()
313 .skip(1)
314 .take(spacer_count.saturating_sub(2))
315 {
316 solver
317 .add_constraint(
318 var | EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
319 spacing_value,
320 )
321 .ok()?;
322 }
323 if spacer_count >= 2 {
324 let first = spacer_vars[0];
325 let last = spacer_vars[spacer_count - 1];
326 solver
327 .add_constraint(
328 (first - last) | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
329 )
330 .ok()?;
331 }
332 },
333 | Flex::SpaceBetween => {
334 if let Some(&first) = spacer_vars.first() {
335 solver
336 .add_constraint(
337 first | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
338 )
339 .ok()?;
340 }
341 if let Some(&last) = spacer_vars.last() {
342 solver
343 .add_constraint(
344 last | EQ(kasuari::Strength::new(strengths::REQUIRED)) | 0.0,
345 )
346 .ok()?;
347 }
348 if spacer_count >= 3 {
349 let first_internal = spacer_vars[1];
350 for &var in spacer_vars.iter().skip(2).take(spacer_count - 3) {
351 solver
352 .add_constraint(
353 (var - first_internal) |
354 EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
355 0.0,
356 )
357 .ok()?;
358 }
359 }
360 },
361 | Flex::SpaceAround => {
362 if spacer_count >= 3 {
363 let first = spacer_vars[0];
364 let last = spacer_vars[spacer_count - 1];
365 let first_internal = spacer_vars[1];
366 solver
367 .add_constraint(
368 (first * 2.0 - first_internal) |
369 EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
370 0.0,
371 )
372 .ok()?;
373 solver
374 .add_constraint(
375 (last * 2.0 - first_internal) |
376 EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
377 0.0,
378 )
379 .ok()?;
380 for &var in spacer_vars.iter().skip(2).take(spacer_count - 3) {
381 solver
382 .add_constraint(
383 (var - first_internal) |
384 EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
385 0.0,
386 )
387 .ok()?;
388 }
389 }
390 },
391 | Flex::SpaceEvenly => {
392 if spacer_count >= 2 {
393 let first = spacer_vars[0];
394 for &var in spacer_vars.iter().skip(1) {
395 solver
396 .add_constraint(
397 (var - first) |
398 EQ(kasuari::Strength::new(strengths::SPACER_SIZE_EQ)) |
399 0.0,
400 )
401 .ok()?;
402 }
403 }
404 },
405 }
406
407 for (i, constraint) in self.constraints.iter().enumerate() {
411 let var = segment_vars[i];
412 match constraint {
413 | Constraint::Fill(priority) => {
414 let strength =
415 kasuari::Strength::new(strengths::FILL_GROW * (*priority as f64));
416 solver
417 .add_constraint(var | EQ(strength) | total as f64)
418 .ok()?;
419 },
420 | Constraint::Min(_) => {
421 solver
422 .add_constraint(
423 var | EQ(kasuari::Strength::new(strengths::GROW)) | total as f64,
424 )
425 .ok()?;
426 },
427 | _ => {},
428 }
429 }
430
431 if self.flex != Flex::Legacy {
433 for &var in &segment_vars {
434 solver
435 .add_constraint(
436 var | EQ(kasuari::Strength::new(strengths::ALL_SEGMENT_GROW)) |
437 total as f64,
438 )
439 .ok()?;
440 }
441 }
442
443 solver.fetch_changes();
445
446 let mut rects = Vec::with_capacity(segment_count);
447 let mut current: u16 = 0;
448
449 for i in 0..segment_count {
450 let spacer =
451 (solver.get_value(spacer_vars[i]) / FLOAT_PRECISION_MULTIPLIER).round() as u16;
452 current = current.saturating_add(spacer);
453
454 let size =
455 (solver.get_value(segment_vars[i]) / FLOAT_PRECISION_MULTIPLIER).round() as u16;
456
457 let rect = match self.direction {
458 | Direction::Horizontal => {
459 Rect::new(inner.x.saturating_add(current), inner.y, size, inner.height)
460 },
461 | Direction::Vertical => {
462 Rect::new(inner.x, inner.y.saturating_add(current), inner.width, size)
463 },
464 };
465 rects.push(rect);
466
467 current = current.saturating_add(size);
468 }
469
470 Some(rects)
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn layout_vertical_split_length() {
480 let layout = Layout::vertical([Constraint::Length(5), Constraint::Length(5)]);
481 let rects = layout.split(Rect::new(0, 0, 10, 10));
482 assert_eq!(rects.len(), 2);
483 assert_eq!(rects[0].height, 5);
484 assert_eq!(rects[1].height, 5);
485 }
486
487 #[test]
488 fn layout_horizontal_split_length() {
489 let layout = Layout::horizontal([Constraint::Length(5), Constraint::Length(5)]);
490 let rects = layout.split(Rect::new(0, 0, 10, 10));
491 assert_eq!(rects.len(), 2);
492 assert_eq!(rects[0].width, 5);
493 assert_eq!(rects[1].width, 5);
494 }
495
496 #[test]
497 fn layout_split_with_margin() {
498 let layout = Layout::vertical([Constraint::Length(5), Constraint::Length(5)]).margin(1);
499 let rects = layout.split(Rect::new(0, 0, 10, 10));
500 assert_eq!(rects.len(), 2);
501 assert_eq!(rects[0].y, 1);
502 assert_eq!(rects[0].width, 8);
503 }
504
505 #[test]
506 fn layout_split_empty_area() {
507 let layout = Layout::vertical([Constraint::Length(5)]);
508 let rects = layout.split(Rect::ZERO);
509 assert_eq!(rects.len(), 1);
510 assert_eq!(rects[0], Rect::ZERO);
511 }
512
513 #[test]
514 fn layout_builder_api() {
515 let layout = Layout::default()
516 .direction(Direction::Horizontal)
517 .constraints([Constraint::Length(10)])
518 .margin(2)
519 .flex(Flex::Center)
520 .spacing(1);
521 assert_eq!(layout.direction, Direction::Horizontal);
522 assert_eq!(layout.constraints, vec![Constraint::Length(10)]);
523 assert_eq!(layout.margin, Margin::new(2, 2));
524 assert_eq!(layout.flex, Flex::Center);
525 assert_eq!(layout.spacing, Spacing::Space(1));
526 }
527
528 #[test]
529 fn layout_areas_const_generic() {
530 let layout = Layout::vertical([Constraint::Length(5), Constraint::Length(5)]);
531 let areas: [Rect; 2] = layout.areas(Rect::new(0, 0, 10, 10));
532 assert_eq!(areas[0].height, 5);
533 assert_eq!(areas[1].height, 5);
534 }
535}