1use crate::node::Axis;
21use crate::screen::Rect;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum Size {
29 Fixed(u16),
31 Percent(u16),
33 Flex(u16),
35 Auto,
39 Ratio(u32, u32),
45}
46
47pub(crate) fn ratio_of(total: u32, a: u32, b: u32) -> u32 {
50 if b == 0 {
51 return 0;
52 }
53 (u64::from(total) * u64::from(a) / u64::from(b)).min(u64::from(total)) as u32
54}
55
56fn ratios_of(total: u32, ratios: &[(u32, u32)]) -> u32 {
59 fn gcd(mut a: u128, mut b: u128) -> u128 {
60 while b != 0 {
61 (a, b) = (b, a % b);
62 }
63 a.max(1)
64 }
65 let (mut num, mut den) = (0u128, 1u128);
66 for &(a, b) in ratios.iter().filter(|(_, b)| *b != 0) {
67 let (a, b) = (u128::from(a.min(b)), u128::from(b));
68 let sum = num
69 .checked_mul(b)
70 .and_then(|n| a.checked_mul(den).and_then(|m| n.checked_add(m)));
71 match (sum, den.checked_mul(b)) {
72 (Some(n), Some(d)) => {
73 let g = gcd(n, d);
74 (num, den) = (n / g, d / g);
75 }
76 _ => {
79 let exact: f64 = ratios
80 .iter()
81 .filter(|(_, b)| *b != 0)
82 .map(|&(a, b)| f64::from(a.min(b)) / f64::from(b))
83 .sum();
84 return (f64::from(total) * exact).floor().min(f64::from(total)) as u32;
85 }
86 }
87 }
88 (u128::from(total) * num / den).min(u128::from(total)) as u32
89}
90
91#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
94pub enum TextAlign {
95 #[default]
97 Left,
98 Center,
100 Right,
102 Justify,
105}
106
107#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
111pub enum Align {
112 Start,
115 Center,
117 End,
119 #[default]
121 Stretch,
122}
123
124#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
128pub enum Justify {
129 #[default]
131 Start,
132 Center,
134 End,
136 SpaceBetween,
139 SpaceAround,
142 SpaceEvenly,
144}
145
146#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
149pub enum AlignContent {
150 Start,
151 Center,
152 End,
153 SpaceBetween,
154 SpaceAround,
155 SpaceEvenly,
156 #[default]
158 Stretch,
159}
160
161#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
166pub struct StackLayout {
167 pub gap: u16,
168 pub align_items: Align,
169 pub justify: Justify,
170 pub wrap: bool,
171 pub align_content: AlignContent,
172 pub solver: crate::solver::Solver,
174}
175
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
188#[must_use = "builder methods return a new value; it does nothing until it is used"]
189pub struct Track {
190 pub(crate) size: Size,
191 pub(crate) min: u16,
192 pub(crate) max: u16,
193 pub(crate) content: u16,
195}
196
197impl Track {
198 pub fn new(size: Size) -> Track {
200 Track {
201 size,
202 min: 0,
203 max: u16::MAX,
204 content: 0,
205 }
206 }
207
208 pub fn min(mut self, cells: u16) -> Track {
210 self.min = cells;
211 self
212 }
213
214 pub fn max(mut self, cells: u16) -> Track {
216 self.max = cells;
217 self
218 }
219
220 pub fn content(mut self, cells: u16) -> Track {
222 self.content = cells;
223 self
224 }
225
226 fn clamp(&self, value: u32) -> u32 {
227 value.min(self.max as u32).max(self.min as u32)
229 }
230}
231
232pub(crate) fn solve(total: u16, gap: u16, tracks: &[Track]) -> Vec<u16> {
235 let n = tracks.len();
236 if n == 0 {
237 return Vec::new();
238 }
239 let total = (total as u32).saturating_sub(gap as u32 * (n as u32 - 1));
240 let ratios: Vec<(u32, u32)> = tracks
244 .iter()
245 .filter_map(|t| match t.size {
246 Size::Ratio(a, b) => Some((a, b)),
247 _ => None,
248 })
249 .collect();
250 let mut ratio_rest = ratios_of(total, &ratios).saturating_sub(
251 ratios
252 .iter()
253 .map(|&(a, b)| ratio_of(total, a, b))
254 .sum::<u32>(),
255 );
256 let last_ratio = tracks
257 .iter()
258 .rposition(|t| matches!(t.size, Size::Ratio(..)));
259 let mut sizes: Vec<u32> = tracks
260 .iter()
261 .enumerate()
262 .map(|(i, t)| match t.size {
263 Size::Fixed(cells) => t.clamp(cells as u32),
264 Size::Percent(p) => t.clamp(total * p.min(100) as u32 / 100),
265 Size::Ratio(a, b) => {
266 let rest = if Some(i) == last_ratio {
267 std::mem::take(&mut ratio_rest)
268 } else {
269 0
270 };
271 t.clamp(ratio_of(total, a, b) + rest)
272 }
273 Size::Auto => t.clamp(t.content as u32),
274 Size::Flex(_) => 0,
275 })
276 .collect();
277 let mut frozen: Vec<bool> = tracks
278 .iter()
279 .map(|t| !matches!(t.size, Size::Flex(_)))
280 .collect();
281 loop {
284 let open: Vec<usize> = (0..n).filter(|&i| !frozen[i]).collect();
285 if open.is_empty() {
286 break;
287 }
288 let used: u32 = (0..n).filter(|&i| frozen[i]).map(|i| sizes[i]).sum();
289 let free = total.saturating_sub(used);
290 let weight = |i: usize| match tracks[i].size {
291 Size::Flex(w) => w as u32,
292 _ => 0,
293 };
294 let weights: u32 = open.iter().map(|&i| weight(i)).sum();
295 let mut shares = vec![0u32; n];
296 let mut left = free;
297 let last = open.iter().rev().copied().find(|&i| weight(i) > 0);
299 for &i in &open {
300 if weight(i) == 0 {
301 continue;
302 }
303 let share = if Some(i) == last {
304 left
305 } else {
306 free * weight(i) / weights
307 };
308 left -= share;
309 shares[i] = share;
310 }
311 let fitted: Vec<u32> = (0..n).map(|i| tracks[i].clamp(shares[i])).collect();
315 let net: i64 = open
316 .iter()
317 .map(|&i| fitted[i] as i64 - shares[i] as i64)
318 .sum();
319 let mut clamped = false;
320 for &i in &open {
321 let hit = match net.signum() {
322 1 => fitted[i] > shares[i],
323 -1 => fitted[i] < shares[i],
324 _ => fitted[i] != shares[i],
325 };
326 if hit {
327 sizes[i] = fitted[i];
328 frozen[i] = true;
329 clamped = true;
330 }
331 }
332 if !clamped {
333 for &i in &open {
334 sizes[i] = shares[i];
335 }
336 break;
337 }
338 }
339 let mut over = sizes.iter().sum::<u32>().saturating_sub(total);
341 for floor in [true, false] {
342 for i in (0..n).rev() {
343 if over == 0 {
344 break;
345 }
346 let keep = if floor { tracks[i].min as u32 } else { 0 };
347 let cut = sizes[i].saturating_sub(keep).min(over);
348 sizes[i] -= cut;
349 over -= cut;
350 }
351 }
352 sizes.into_iter().map(|s| s as u16).collect()
353}
354
355pub(crate) fn grow_for_span(
361 tracks: &mut [Track],
362 total: u16,
363 gap: u16,
364 from: usize,
365 n: usize,
366 need: u16,
367) {
368 let span = from..(from + n).min(tracks.len());
369 let held: u32 = tracks[span.clone()]
370 .iter()
371 .map(|t| match t.size {
372 Size::Auto => t.content as u32,
373 Size::Fixed(cells) => cells as u32,
374 Size::Percent(p) => total as u32 * p.min(100) as u32 / 100,
375 Size::Ratio(a, b) => ratio_of(total as u32, a, b),
376 Size::Flex(_) => 0,
377 })
378 .sum::<u32>()
379 + gap as u32 * (span.len().saturating_sub(1)) as u32;
380 let short = (need as u32).saturating_sub(held);
381 let content: Vec<usize> = span.filter(|&i| tracks[i].size == Size::Auto).collect();
382 if short == 0 || content.is_empty() {
383 return;
384 }
385 let each = short / content.len() as u32;
386 let last = *content.last().expect("not empty");
387 for &i in &content {
388 let extra = if i == last {
389 short - each * (content.len() as u32 - 1)
390 } else {
391 each
392 };
393 tracks[i].content = (tracks[i].content as u32 + extra).min(u16::MAX as u32) as u16;
394 }
395}
396
397pub(crate) fn offsets(start: u16, gap: u16, sizes: &[u16]) -> Vec<u16> {
399 let mut at = start;
400 sizes
401 .iter()
402 .map(|&size| {
403 let here = at;
404 at = at.saturating_add(size).saturating_add(gap);
405 here
406 })
407 .collect()
408}
409
410fn spread(justify: Justify, free: u32, i: u32, k: u32) -> u32 {
415 let (free, i, k) = (free as u64, i as u64, k as u64);
417 let share = match justify {
418 Justify::Start => 0,
419 Justify::Center => free / 2,
420 Justify::End => free,
421 Justify::SpaceBetween if k > 1 => free * i / (k - 1),
422 Justify::SpaceBetween => 0,
423 Justify::SpaceAround => free * (2 * i + 1) / (2 * k),
424 Justify::SpaceEvenly => free * (i + 1) / (k + 1),
425 };
426 share as u32
427}
428
429pub(crate) fn justify_offsets(
432 start: u16,
433 total: u16,
434 gap: u16,
435 sizes: &[u16],
436 justify: Justify,
437) -> Vec<u16> {
438 let k = sizes.len() as u32;
439 let used: u64 =
440 sizes.iter().map(|&n| n as u64).sum::<u64>() + gap as u64 * k.saturating_sub(1) as u64;
441 let free = (total as u64).saturating_sub(used) as u32;
442 let mut at = start as u64;
443 sizes
444 .iter()
445 .enumerate()
446 .map(|(i, &size)| {
447 let here = at + spread(justify, free, i as u32, k) as u64;
448 at += size as u64 + gap as u64;
449 here.min(u16::MAX as u64) as u16
450 })
451 .collect()
452}
453
454pub(crate) fn align_cross(at: u16, line: u16, content: u16, align: Align) -> (u16, u16) {
457 let size = content.min(line);
458 let free = line - size;
459 match align {
460 Align::Stretch => (at, line),
461 Align::Start => (at, size),
462 Align::Center => (at.saturating_add(free / 2), size),
463 Align::End => (at.saturating_add(free), size),
464 }
465}
466
467pub(crate) fn break_lines(total: u16, gap: u16, bases: &[u16]) -> Vec<std::ops::Range<usize>> {
471 let mut lines = Vec::new();
472 let mut first = 0;
473 let mut used = 0u32;
474 for (i, &base) in bases.iter().enumerate() {
475 let need = if i == first {
476 base as u32
477 } else {
478 used + gap as u32 + base as u32
479 };
480 if i > first && need > total as u32 {
481 lines.push(first..i);
482 first = i;
483 used = base as u32;
484 } else {
485 used = need;
486 }
487 }
488 if first < bases.len() {
489 lines.push(first..bases.len());
490 }
491 lines
492}
493
494pub(crate) fn place_lines(
498 start: u16,
499 total: u16,
500 gap: u16,
501 lines: &[u16],
502 align: AlignContent,
503) -> Vec<(u16, u16)> {
504 let k = lines.len() as u32;
505 let used: u64 =
506 lines.iter().map(|&n| n as u64).sum::<u64>() + gap as u64 * k.saturating_sub(1) as u64;
507 let free = (total as u64).saturating_sub(used) as u32;
508 let justify = match align {
509 AlignContent::Start | AlignContent::Stretch => Justify::Start,
510 AlignContent::Center => Justify::Center,
511 AlignContent::End => Justify::End,
512 AlignContent::SpaceBetween => Justify::SpaceBetween,
513 AlignContent::SpaceAround => Justify::SpaceAround,
514 AlignContent::SpaceEvenly => Justify::SpaceEvenly,
515 };
516 let mut at = start as u64;
517 lines
518 .iter()
519 .enumerate()
520 .map(|(i, &size)| {
521 let i = i as u32;
522 let grow = if align == AlignContent::Stretch {
525 let (free, i, k) = (free as u64, i as u64, k as u64);
526 (free * (i + 1) / k - free * i / k) as u32
527 } else {
528 0
529 };
530 let here = at + spread(justify, free, i, k) as u64;
531 let size = size as u32 + grow;
532 at += size as u64 + gap as u64;
533 (
534 here.min(u16::MAX as u64) as u16,
535 size.min(u16::MAX as u32) as u16,
536 )
537 })
538 .collect()
539}
540
541#[derive(Clone, Copy, Debug, PartialEq, Eq)]
544pub(crate) struct Cellspan {
545 pub column: usize,
546 pub row: usize,
547 pub columns: usize,
548 pub rows: usize,
549}
550
551pub(crate) fn place(columns: usize, spans: &[(u16, u16)]) -> (Vec<Cellspan>, usize) {
555 let columns = columns.max(1);
556 let mut taken: Vec<Vec<bool>> = Vec::new();
557 let (mut row, mut column) = (0usize, 0usize);
558 let mut places = Vec::with_capacity(spans.len());
559 for &(wide, tall) in spans {
560 let wide = (wide.max(1) as usize).min(columns);
561 let tall = tall.max(1) as usize;
562 loop {
563 if column + wide > columns {
564 row += 1;
565 column = 0;
566 continue;
567 }
568 let fits = (row..row + tall).all(|r| {
569 (column..column + wide).all(|c| !taken.get(r).is_some_and(|cells| cells[c]))
570 });
571 if fits {
572 break;
573 }
574 column += 1;
575 }
576 while taken.len() < row + tall {
577 taken.push(vec![false; columns]);
578 }
579 for cells in &mut taken[row..row + tall] {
580 for cell in &mut cells[column..column + wide] {
581 *cell = true;
582 }
583 }
584 places.push(Cellspan {
585 column,
586 row,
587 columns: wide,
588 rows: tall,
589 });
590 column += wide;
591 }
592 (places, taken.len())
593}
594
595pub fn split(rect: Rect, axis: Axis, tracks: &[Track], flow: StackLayout) -> Vec<Rect> {
615 split_with_spacers(rect, axis, tracks, flow).0
616}
617
618pub fn split_with_spacers(
638 rect: Rect,
639 axis: Axis,
640 tracks: &[Track],
641 flow: StackLayout,
642) -> (Vec<Rect>, Vec<Rect>) {
643 let (start, total, across, breadth) = match axis {
644 Axis::Vertical => (rect.y, rect.height, rect.x, rect.width),
645 Axis::Horizontal => (rect.x, rect.width, rect.y, rect.height),
646 };
647 let piece = |at: u16, length: u16| match axis {
648 Axis::Vertical => Rect::new(across, at, breadth, length),
649 Axis::Horizontal => Rect::new(at, across, length, breadth),
650 };
651 let (at, sizes): (Vec<u16>, Vec<u16>) = if flow.solver == crate::solver::Solver::Native {
652 let sizes = solve(total, flow.gap, tracks);
653 let at = justify_offsets(start, total, flow.gap, &sizes, flow.justify);
654 (at, sizes)
655 } else {
656 crate::solver::split(start, total, flow.gap, tracks, flow.solver, flow.justify)
657 .into_iter()
658 .unzip()
659 };
660 let end = start.saturating_add(total);
661 let areas: Vec<Rect> = at
662 .iter()
663 .zip(&sizes)
664 .map(|(&at, &size)| {
665 let at = at.min(end);
666 piece(at, size.min(end - at))
667 })
668 .collect();
669 let mut spacers = Vec::with_capacity(areas.len() + 1);
672 let mut from = start;
673 for (&at, &size) in at.iter().zip(&sizes) {
674 let to = at.clamp(from, end);
675 spacers.push(piece(from, to - from));
676 from = at.saturating_add(size).clamp(to, end);
677 }
678 spacers.push(piece(from, end - from));
679 (areas, spacers)
680}
681
682#[derive(Clone, Debug, Default, PartialEq, Eq)]
686pub struct Squarified {
687 pub tiles: Vec<Rect>,
688 pub rest: Option<(Rect, usize)>,
689}
690
691pub fn squarify(rect: Rect, weights: &[f64], min: (u16, u16), aspect: f64) -> Squarified {
721 let mut out = Squarified {
722 tiles: vec![Rect::default(); weights.len()],
723 rest: None,
724 };
725 let mut order: Vec<usize> = (0..weights.len())
726 .filter(|&i| weights[i].is_finite() && weights[i] > 0.0)
727 .collect();
728 order.sort_by(|&a, &b| weights[b].total_cmp(&weights[a]));
729 let total: f64 = order.iter().map(|&i| weights[i]).sum();
730 if rect.is_empty() || order.is_empty() || total <= 0.0 {
731 return out;
732 }
733 let aspect = if aspect.is_finite() && aspect > 0.0 {
734 aspect
735 } else {
736 1.0
737 };
738 let fits = |r: Rect| r.width >= min.0.max(1) && r.height >= min.1.max(1);
739 let cells = rect.width as f64 * rect.height as f64;
741 let least = min.0.max(1) as f64 * min.1.max(1) as f64;
742 let mut k = order
743 .iter()
744 .take_while(|&&i| weights[i] / total * cells >= least)
745 .count();
746 if k == order.len() {
747 let tiles = squarify_tiles(rect, &order, weights, None, aspect);
748 if tiles.iter().all(|(_, r)| fits(*r)) {
749 for (i, r) in tiles {
750 out.tiles[i] = r;
751 }
752 return out;
753 }
754 k -= 1;
755 }
756 loop {
758 let rest: f64 = order[k..].iter().map(|&i| weights[i]).sum();
760 let mut rest = rest.max(total * 2.0 * least / cells);
761 let mut tiles = squarify_tiles(rect, &order[..k], weights, Some(rest), aspect);
762 for _ in 0..8 {
765 let Some(&(_, r)) = tiles.iter().find(|(i, _)| *i == usize::MAX) else {
766 break;
767 };
768 if k == 0 || fits(r) || !tiles.iter().all(|(i, r)| *i == usize::MAX || fits(*r)) {
769 break;
770 }
771 let grow = |have: u16, want: u16| want.max(1) as f64 / have.max(1) as f64;
772 let by = grow(r.width, min.0).max(grow(r.height, min.1)).max(1.1);
773 rest *= by;
774 tiles = squarify_tiles(rect, &order[..k], weights, Some(rest), aspect);
775 }
776 if k == 0 || tiles.iter().all(|(_, r)| fits(*r)) {
777 for (i, r) in tiles {
778 match i {
779 usize::MAX => out.rest = Some((r, order.len() - k)),
780 i => out.tiles[i] = r,
781 }
782 }
783 return out;
784 }
785 k -= 1;
786 }
787}
788
789fn squarify_tiles(
792 rect: Rect,
793 order: &[usize],
794 weights: &[f64],
795 rest: Option<f64>,
796 aspect: f64,
797) -> Vec<(usize, Rect)> {
798 let mut items: Vec<(usize, f64)> = order.iter().map(|&i| (i, weights[i])).collect();
799 if let Some(rest) = rest.filter(|r| *r > 0.0) {
800 items.push((usize::MAX, rest));
801 }
802 let (width, height) = (rect.width as f64, rect.height as f64 * aspect);
804 let total: f64 = items.iter().map(|(_, w)| w).sum();
805 let scale = width * height / total;
806 let areas: Vec<(usize, f64)> = items.iter().map(|&(i, w)| (i, w * scale)).collect();
807 let mut free = (0.0, 0.0, width, height);
808 let mut placed: Vec<(usize, (f64, f64, f64, f64))> = Vec::new();
809 let worst = |row: &[(usize, f64)], side: f64| -> f64 {
811 let sum: f64 = row.iter().map(|(_, a)| a).sum();
812 row.iter()
813 .map(|(_, a)| {
814 let along = side * side * a / (sum * sum);
815 along.max(1.0 / along)
816 })
817 .fold(0.0, f64::max)
818 };
819 let mut lay = |row: &[(usize, f64)], free: &mut (f64, f64, f64, f64)| {
820 let (x, y, w, h) = *free;
821 let sum: f64 = row.iter().map(|(_, a)| a).sum();
822 if w >= h {
823 let across = if h > 0.0 { sum / h } else { 0.0 };
825 let mut at = y;
826 for &(i, a) in row {
827 let down = if across > 0.0 { a / across } else { 0.0 };
828 placed.push((i, (x, at, across, down)));
829 at += down;
830 }
831 *free = (x + across, y, (w - across).max(0.0), h);
832 } else {
833 let down = if w > 0.0 { sum / w } else { 0.0 };
835 let mut at = x;
836 for &(i, a) in row {
837 let across = if down > 0.0 { a / down } else { 0.0 };
838 placed.push((i, (at, y, across, down)));
839 at += across;
840 }
841 *free = (x, y + down, w, (h - down).max(0.0));
842 }
843 };
844 let mut row: Vec<(usize, f64)> = Vec::new();
845 for &item in &areas {
846 let side = free.2.min(free.3);
847 let mut longer = row.clone();
848 longer.push(item);
849 if row.is_empty() || worst(&longer, side) <= worst(&row, side) {
850 row = longer;
851 } else {
852 lay(&row, &mut free);
853 row = vec![item];
854 }
855 }
856 if !row.is_empty() {
857 lay(&row, &mut free);
858 }
859 let x_of = |x: f64| {
861 (rect.x as f64 + x)
862 .round()
863 .clamp(rect.x as f64, rect.right() as f64) as u16
864 };
865 let y_of = |y: f64| {
866 (rect.y as f64 + y / aspect)
867 .round()
868 .clamp(rect.y as f64, rect.bottom() as f64) as u16
869 };
870 placed
871 .into_iter()
872 .map(|(i, (x, y, w, h))| {
873 let (left, top) = (x_of(x), y_of(y));
874 let (right, bottom) = (x_of(x + w), y_of(y + h));
875 (i, Rect::new(left, top, right - left, bottom - top))
876 })
877 .collect()
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883
884 fn track(size: Size) -> Track {
885 Track::new(size)
886 }
887
888 #[test]
889 fn three_thirds_fill_the_room_exactly() {
890 let third = track(Size::Ratio(1, 3));
891 assert_eq!(solve(100, 0, &[third, third, third]), vec![33, 33, 34]);
892 assert_eq!(solve(100, 1, &[third, third, third]), vec![32, 32, 34]);
894 assert_eq!(solve(10, 0, &[track(Size::Ratio(1, 0))]), vec![0]);
896 assert_eq!(solve(10, 0, &[track(Size::Ratio(3, 2))]), vec![10]);
897 }
898
899 #[test]
900 fn ratios_mix_with_fixed_and_flexible_tracks() {
901 let tracks = [
902 track(Size::Fixed(10)),
903 track(Size::Ratio(1, 4)),
904 track(Size::Flex(1)),
905 track(Size::Ratio(1, 4)),
906 ];
907 assert_eq!(solve(100, 0, &tracks), vec![10, 25, 40, 25]);
909 let tracks = [track(Size::Ratio(1, 3)), track(Size::Ratio(1, 6))];
912 assert_eq!(solve(50, 0, &tracks), vec![16, 9]);
913 let mut capped = track(Size::Ratio(1, 2));
914 capped.max = 10;
915 assert_eq!(solve(40, 0, &[capped, track(Size::Flex(1))]), vec![10, 30]);
916 }
917
918 #[test]
919 fn spacers_are_the_room_between_and_around() {
920 let rect = Rect::new(0, 0, 20, 1);
921 let tracks = [track(Size::Fixed(4)), track(Size::Fixed(6))];
922 let flow = |justify| StackLayout {
923 justify,
924 ..StackLayout::default()
925 };
926 let (areas, spacers) =
927 split_with_spacers(rect, Axis::Horizontal, &tracks, flow(Justify::SpaceBetween));
928 assert_eq!(areas, [Rect::new(0, 0, 4, 1), Rect::new(14, 0, 6, 1)]);
929 assert_eq!(
931 spacers,
932 [
933 Rect::new(0, 0, 0, 1),
934 Rect::new(4, 0, 10, 1),
935 Rect::new(20, 0, 0, 1)
936 ]
937 );
938 let (areas, spacers) =
939 split_with_spacers(rect, Axis::Horizontal, &tracks, flow(Justify::Center));
940 assert_eq!(areas, [Rect::new(5, 0, 4, 1), Rect::new(9, 0, 6, 1)]);
941 assert_eq!(
942 spacers,
943 [
944 Rect::new(0, 0, 5, 1),
945 Rect::new(9, 0, 0, 1),
946 Rect::new(15, 0, 5, 1)
947 ]
948 );
949 let flow = StackLayout {
951 gap: 1,
952 ..StackLayout::default()
953 };
954 let (areas, spacers) = split_with_spacers(
955 Rect::new(2, 1, 5, 10),
956 Axis::Vertical,
957 &[track(Size::Fixed(2)), track(Size::Flex(1))],
958 flow,
959 );
960 assert_eq!(areas, [Rect::new(2, 1, 5, 2), Rect::new(2, 4, 5, 7)]);
961 assert_eq!(spacers[1], Rect::new(2, 3, 5, 1));
962 assert_eq!(spacers[2], Rect::new(2, 11, 5, 0));
963 assert_eq!(
965 split_with_spacers(rect, Axis::Horizontal, &[], flow).1,
966 [rect]
967 );
968 }
969
970 #[test]
971 fn a_grid_of_ratio_tracks_shares_its_width() {
972 let third = track(Size::Ratio(1, 3));
973 assert_eq!(solve(60, 2, &[third, third, third]), vec![18, 18, 20]);
975 }
976
977 #[test]
978 fn flex_shares_what_fixed_and_percent_leave() {
979 let tracks = [
980 track(Size::Fixed(10)),
981 track(Size::Percent(50)),
982 track(Size::Flex(1)),
983 track(Size::Flex(3)),
984 ];
985 assert_eq!(solve(100, 0, &tracks), vec![10, 50, 10, 30]);
986 }
987
988 #[test]
989 fn gaps_come_out_of_the_total() {
990 let tracks = [track(Size::Flex(1)), track(Size::Flex(1))];
991 assert_eq!(solve(21, 1, &tracks), vec![10, 10]);
992 }
993
994 #[test]
995 fn a_clamped_flex_track_gives_its_share_to_the_others() {
996 let mut capped = track(Size::Flex(1));
997 capped.max = 5;
998 let tracks = [capped, track(Size::Flex(1))];
999 assert_eq!(solve(30, 0, &tracks), vec![5, 25]);
1000 let mut floor = track(Size::Flex(1));
1001 floor.min = 20;
1002 let tracks = [floor, track(Size::Flex(1))];
1003 assert_eq!(solve(30, 0, &tracks), vec![20, 10]);
1004 }
1005
1006 #[test]
1007 fn overflow_shrinks_from_the_end_to_the_minimums_first() {
1008 let mut first = track(Size::Fixed(10));
1009 first.min = 4;
1010 let mut second = track(Size::Fixed(10));
1011 second.min = 8;
1012 assert_eq!(solve(16, 0, &[first, second]), vec![8, 8]);
1013 assert_eq!(solve(10, 0, &[first, second]), vec![4, 6]);
1014 assert_eq!(solve(4, 0, &[first, second]), vec![4, 0]);
1015 }
1016
1017 #[test]
1018 fn auto_takes_its_content() {
1019 let mut auto = track(Size::Auto);
1020 auto.content = 3;
1021 assert_eq!(solve(10, 0, &[auto, track(Size::Flex(1))]), vec![3, 7]);
1022 }
1023
1024 #[test]
1025 fn a_span_grows_the_content_tracks_it_covers() {
1026 let mut tracks = [track(Size::Auto), track(Size::Fixed(2)), track(Size::Auto)];
1027 tracks[0].content = 1;
1028 grow_for_span(&mut tracks, 20, 1, 0, 3, 10);
1030 assert_eq!((tracks[0].content, tracks[2].content), (3, 3));
1031 grow_for_span(&mut tracks, 20, 1, 0, 2, 4);
1033 assert_eq!(tracks[0].content, 3);
1034 }
1035
1036 #[test]
1041 fn justify_places_what_the_items_leave() {
1042 let at = |j| justify_offsets(0, 20, 0, &[4, 6], j);
1043 assert_eq!(at(Justify::Start), [0, 4]);
1044 assert_eq!(at(Justify::Center), [5, 9]);
1045 assert_eq!(at(Justify::End), [10, 14]);
1046 assert_eq!(at(Justify::SpaceBetween), [0, 14]);
1047 assert_eq!(at(Justify::SpaceAround), [2, 11]);
1049 assert_eq!(at(Justify::SpaceEvenly), [3, 10]);
1051 }
1052
1053 #[test]
1054 fn justify_counts_gaps_and_never_overflows() {
1055 assert_eq!(
1056 justify_offsets(2, 20, 1, &[4, 6], Justify::End),
1057 [11, 16],
1058 "ends at 2 + 20"
1059 );
1060 for j in [Justify::Center, Justify::End, Justify::SpaceEvenly] {
1062 assert_eq!(justify_offsets(0, 10, 0, &[4, 6], j), [0, 4]);
1063 }
1064 assert_eq!(justify_offsets(0, 10, 0, &[4], Justify::SpaceBetween), [0]);
1066 }
1067
1068 #[test]
1069 fn items_align_across_the_line() {
1070 assert_eq!(align_cross(1, 10, 4, Align::Start), (1, 4));
1071 assert_eq!(align_cross(1, 10, 4, Align::Center), (4, 4));
1072 assert_eq!(align_cross(1, 10, 4, Align::End), (7, 4));
1073 assert_eq!(align_cross(1, 10, 4, Align::Stretch), (1, 10));
1074 assert_eq!(align_cross(0, 3, 8, Align::Center), (0, 3));
1076 }
1077
1078 #[test]
1079 fn lines_break_where_the_next_item_does_not_fit() {
1080 assert_eq!(
1081 break_lines(10, 1, &[4, 4, 4, 9, 11]),
1082 [0..2, 2..3, 3..4, 4..5]
1083 );
1084 assert_eq!(break_lines(10, 1, &[4, 5, 1]), [0..2, 2..3]);
1086 assert!(break_lines(10, 1, &[]).is_empty());
1087 }
1088
1089 #[test]
1090 fn align_content_places_the_lines() {
1091 let lines = |a| place_lines(0, 10, 1, &[2, 3], a);
1093 assert_eq!(lines(AlignContent::Start), [(0, 2), (3, 3)]);
1094 assert_eq!(lines(AlignContent::End), [(4, 2), (7, 3)]);
1095 assert_eq!(lines(AlignContent::Center), [(2, 2), (5, 3)]);
1096 assert_eq!(lines(AlignContent::SpaceBetween), [(0, 2), (7, 3)]);
1097 assert_eq!(lines(AlignContent::Stretch), [(0, 4), (5, 5)]);
1099 }
1100
1101 #[test]
1102 fn places_flow_row_by_row_around_spans() {
1103 let (places, rows) = place(3, &[(2, 1), (1, 2), (1, 1), (1, 1), (3, 1)]);
1104 let at: Vec<(usize, usize)> = places.iter().map(|p| (p.column, p.row)).collect();
1105 assert_eq!(at, vec![(0, 0), (2, 0), (0, 1), (1, 1), (0, 2)]);
1106 assert_eq!(rows, 3);
1107 }
1108}