1use alloc::vec::Vec;
29
30use retroglyph_core::grid::{HasSize, Rect, Size};
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum Constraint {
36 Fixed(u16),
38 Percent(u16),
40 Ratio(u16, u16),
45 Fill(u16),
51 Min(u16),
54 Max(u16),
57}
58
59impl Constraint {
60 fn base(self, total: u16) -> u16 {
65 match self {
66 Self::Fixed(n) | Self::Min(n) => n.min(total),
67 Self::Percent(p) => {
68 let p = u32::from(p.min(100));
69 #[allow(clippy::cast_possible_truncation)]
71 {
72 (u32::from(total) * p / 100) as u16
73 }
74 }
75 Self::Ratio(num, den) => {
76 if den == 0 {
77 0
78 } else {
79 #[allow(clippy::cast_possible_truncation)]
82 {
83 (u32::from(total) * u32::from(num) / u32::from(den)).min(u32::from(total))
84 as u16
85 }
86 }
87 }
88 Self::Fill(_) | Self::Max(_) => 0,
89 }
90 }
91}
92
93const STACK_CAP: usize = 8;
98
99enum SmallBuf<T: Copy + Default, const N: usize> {
105 Stack([T; N], usize),
106 Heap(Vec<T>),
107}
108
109impl<T: Copy + Default, const N: usize> SmallBuf<T, N> {
110 fn with_capacity(cap: usize) -> Self {
113 if cap <= N {
114 Self::Stack([T::default(); N], 0)
115 } else {
116 Self::Heap(Vec::with_capacity(cap))
117 }
118 }
119
120 fn push(&mut self, value: T) {
127 match self {
128 Self::Stack(buf, len) => {
129 buf[*len] = value;
130 *len += 1;
131 }
132 Self::Heap(vec) => vec.push(value),
133 }
134 }
135}
136
137impl<T: Copy + Default, const N: usize> core::ops::Deref for SmallBuf<T, N> {
138 type Target = [T];
139
140 fn deref(&self) -> &[T] {
141 match self {
142 Self::Stack(buf, len) => &buf[..*len],
143 Self::Heap(vec) => vec,
144 }
145 }
146}
147
148impl<T: Copy + Default, const N: usize> core::ops::DerefMut for SmallBuf<T, N> {
149 fn deref_mut(&mut self) -> &mut [T] {
150 match self {
151 Self::Stack(buf, len) => &mut buf[..*len],
152 Self::Heap(vec) => vec,
153 }
154 }
155}
156
157impl<T: Copy + Default, const N: usize> core::ops::Index<usize> for SmallBuf<T, N> {
158 type Output = T;
159
160 fn index(&self, idx: usize) -> &T {
161 &(**self)[idx]
162 }
163}
164
165impl<T: Copy + Default, const N: usize> core::ops::IndexMut<usize> for SmallBuf<T, N> {
166 fn index_mut(&mut self, idx: usize) -> &mut T {
167 &mut (**self)[idx]
168 }
169}
170
171fn solve(total: u16, constraints: &[Constraint]) -> SmallBuf<u16, STACK_CAP> {
173 let mut sizes: SmallBuf<u16, STACK_CAP> = SmallBuf::with_capacity(constraints.len());
174 for c in constraints {
175 sizes.push(c.base(total));
176 }
177
178 let mut used: u16 = 0;
181 for size in sizes.iter_mut() {
182 let room = total.saturating_sub(used);
183 *size = (*size).min(room);
184 used += *size;
185 }
186
187 let mut flexible: SmallBuf<(usize, u16, Option<u16>), STACK_CAP> =
193 SmallBuf::with_capacity(constraints.len());
194 for (i, c) in constraints.iter().enumerate() {
195 match c {
196 Constraint::Fill(weight) => flexible.push((i, *weight, None)),
197 Constraint::Min(_) => flexible.push((i, 1, None)),
198 Constraint::Max(cap) => flexible.push((i, 1, Some(*cap))),
199 Constraint::Fixed(_) | Constraint::Percent(_) | Constraint::Ratio(_, _) => {}
200 }
201 }
202 if !flexible.is_empty() {
203 let remainder = total.saturating_sub(used);
204 let total_weight: u32 = flexible.iter().map(|&(_, w, _)| u32::from(w)).sum();
205 if let Some(total_weight) = core::num::NonZeroU32::new(total_weight) {
206 let mut shares: SmallBuf<u32, STACK_CAP> = SmallBuf::with_capacity(flexible.len());
212 let mut fracs: SmallBuf<u32, STACK_CAP> = SmallBuf::with_capacity(flexible.len());
213 let mut floor_sum: u32 = 0;
214 for &(_, weight, _) in flexible.iter() {
215 let product = u32::from(remainder) * u32::from(weight);
216 let share = product / total_weight;
217 fracs.push(product % total_weight);
218 shares.push(share);
219 floor_sum += share;
220 }
221 let mut leftover = u32::from(remainder).saturating_sub(floor_sum);
222 let mut order: SmallBuf<usize, STACK_CAP> = SmallBuf::with_capacity(flexible.len());
223 for idx in 0..flexible.len() {
224 order.push(idx);
225 }
226 order.sort_by(|&a, &b| fracs[b].cmp(&fracs[a]).then(a.cmp(&b)));
227 for &idx in order.iter() {
228 if leftover == 0 {
229 break;
230 }
231 shares[idx] += 1;
232 leftover -= 1;
233 }
234 for (k, &(i, _, cap)) in flexible.iter().enumerate() {
235 #[allow(clippy::cast_possible_truncation)]
238 let share = shares[k] as u16;
239 let grown = sizes[i].saturating_add(share);
240 sizes[i] = cap.map_or(grown, |max| grown.min(max));
241 }
242 }
243 }
244
245 sizes
246}
247
248fn solve_n<const N: usize>(total: u16, constraints: &[Constraint; N]) -> [u16; N] {
253 let mut sizes = [0u16; N];
254 for (i, c) in constraints.iter().enumerate() {
255 sizes[i] = c.base(total);
256 }
257
258 let mut used: u16 = 0;
260 for size in &mut sizes {
261 let room = total.saturating_sub(used);
262 *size = (*size).min(room);
263 used += *size;
264 }
265
266 let mut flexible: [(usize, u16, Option<u16>); N] = [(0, 0, None); N];
269 let mut flex_len = 0usize;
270 for (i, c) in constraints.iter().enumerate() {
271 match c {
272 Constraint::Fill(weight) => {
273 flexible[flex_len] = (i, *weight, None);
274 flex_len += 1;
275 }
276 Constraint::Min(_) => {
277 flexible[flex_len] = (i, 1, None);
278 flex_len += 1;
279 }
280 Constraint::Max(cap) => {
281 flexible[flex_len] = (i, 1, Some(*cap));
282 flex_len += 1;
283 }
284 Constraint::Fixed(_) | Constraint::Percent(_) | Constraint::Ratio(_, _) => {}
285 }
286 }
287 if flex_len > 0 {
288 let remainder = total.saturating_sub(used);
289 let total_weight: u32 = flexible[..flex_len]
290 .iter()
291 .map(|&(_, w, _)| u32::from(w))
292 .sum();
293 if let Some(total_weight) = core::num::NonZeroU32::new(total_weight) {
294 let mut shares = [0u32; N];
295 let mut fracs = [0u32; N];
296 let mut floor_sum: u32 = 0;
297 for (k, &(_, weight, _)) in flexible[..flex_len].iter().enumerate() {
298 let product = u32::from(remainder) * u32::from(weight);
299 let share = product / total_weight;
300 fracs[k] = product % total_weight;
301 shares[k] = share;
302 floor_sum += share;
303 }
304 let mut leftover = u32::from(remainder).saturating_sub(floor_sum);
305 let mut order = [0usize; N];
306 for (idx, slot) in order[..flex_len].iter_mut().enumerate() {
307 *slot = idx;
308 }
309 order[..flex_len].sort_by(|&a, &b| fracs[b].cmp(&fracs[a]).then(a.cmp(&b)));
310 for &idx in &order[..flex_len] {
311 if leftover == 0 {
312 break;
313 }
314 shares[idx] += 1;
315 leftover -= 1;
316 }
317 for (k, &(i, _, cap)) in flexible[..flex_len].iter().enumerate() {
318 #[allow(clippy::cast_possible_truncation)]
321 let share = shares[k] as u16;
322 let grown = sizes[i].saturating_add(share);
323 sizes[i] = cap.map_or(grown, |max| grown.min(max));
324 }
325 }
326 }
327
328 sizes
329}
330
331#[must_use]
352pub fn split_v(area: Rect, constraints: &[Constraint]) -> Vec<Rect> {
353 let sizes = solve(area.height(), constraints);
354 let mut y = area.top();
355 sizes
356 .iter()
357 .copied()
358 .map(|h| {
359 let rect = Rect::new(area.left(), y, area.width(), h);
360 y = y.saturating_add(h);
361 rect
362 })
363 .collect()
364}
365
366#[must_use]
388pub fn split_v_n<const N: usize>(area: Rect, constraints: [Constraint; N]) -> [Rect; N] {
389 let sizes = solve_n(area.height(), &constraints);
390 let mut y = area.top();
391 core::array::from_fn(|i| {
392 let h = sizes[i];
393 let rect = Rect::new(area.left(), y, area.width(), h);
394 y = y.saturating_add(h);
395 rect
396 })
397}
398
399#[must_use]
419pub fn split_h(area: Rect, constraints: &[Constraint]) -> Vec<Rect> {
420 let sizes = solve(area.width(), constraints);
421 let mut x = area.left();
422 sizes
423 .iter()
424 .copied()
425 .map(|w| {
426 let rect = Rect::new(x, area.top(), w, area.height());
427 x = x.saturating_add(w);
428 rect
429 })
430 .collect()
431}
432
433#[must_use]
450pub fn split_h_n<const N: usize>(area: Rect, constraints: [Constraint; N]) -> [Rect; N] {
451 let sizes = solve_n(area.width(), &constraints);
452 let mut x = area.left();
453 core::array::from_fn(|i| {
454 let w = sizes[i];
455 let rect = Rect::new(x, area.top(), w, area.height());
456 x = x.saturating_add(w);
457 rect
458 })
459}
460
461#[derive(Clone, Copy, Debug, PartialEq, Eq)]
468#[non_exhaustive]
469pub enum Spacing {
470 Space(u16),
475 Overlap(u16),
481}
482
483impl Spacing {
484 const fn cells(self) -> u16 {
486 match self {
487 Self::Space(n) | Self::Overlap(n) => n,
488 }
489 }
490}
491
492impl From<u16> for Spacing {
493 fn from(n: u16) -> Self {
495 Self::Space(n)
496 }
497}
498
499#[must_use]
533pub fn split_h_spaced(
534 area: Rect,
535 constraints: &[Constraint],
536 spacing: impl Into<Spacing>,
537) -> (Vec<Rect>, Vec<Rect>) {
538 let spacing = spacing.into();
539 if spacing.cells() == 0 || constraints.len() < 2 {
540 return (split_h(area, constraints), Vec::new());
541 }
542 let sizes = solve(
543 spaced_total(area.width(), constraints.len(), spacing),
544 constraints,
545 );
546 let mut x = area.left();
547 let mut panes = Vec::with_capacity(sizes.len());
548 let mut gaps = Vec::with_capacity(sizes.len().saturating_sub(1));
549 let last = sizes.len().saturating_sub(1);
550 for (i, w) in sizes.iter().copied().enumerate() {
551 panes.push(Rect::new(x, area.top(), w, area.height()));
552 if i < last {
553 let (gx, gw) = spacer_span(x, w, spacing);
554 gaps.push(Rect::new(gx, area.top(), gw, area.height()));
555 }
556 x = step(x, w, spacing);
557 }
558 (panes, gaps)
559}
560
561#[must_use]
567pub fn split_v_spaced(
568 area: Rect,
569 constraints: &[Constraint],
570 spacing: impl Into<Spacing>,
571) -> (Vec<Rect>, Vec<Rect>) {
572 let spacing = spacing.into();
573 if spacing.cells() == 0 || constraints.len() < 2 {
574 return (split_v(area, constraints), Vec::new());
575 }
576 let sizes = solve(
577 spaced_total(area.height(), constraints.len(), spacing),
578 constraints,
579 );
580 let mut y = area.top();
581 let mut panes = Vec::with_capacity(sizes.len());
582 let mut gaps = Vec::with_capacity(sizes.len().saturating_sub(1));
583 let last = sizes.len().saturating_sub(1);
584 for (i, h) in sizes.iter().copied().enumerate() {
585 panes.push(Rect::new(area.left(), y, area.width(), h));
586 if i < last {
587 let (gy, gh) = spacer_span(y, h, spacing);
588 gaps.push(Rect::new(area.left(), gy, area.width(), gh));
589 }
590 y = step(y, h, spacing);
591 }
592 (panes, gaps)
593}
594
595const fn spaced_total(total: u16, count: usize, spacing: Spacing) -> u16 {
599 #[allow(clippy::cast_possible_truncation)]
602 let gaps = count as u16 - 1;
603 let delta = spacing.cells().saturating_mul(gaps);
604 match spacing {
605 Spacing::Space(_) => total.saturating_sub(delta),
606 Spacing::Overlap(_) => total.saturating_add(delta),
607 }
608}
609
610const fn step(pos: u16, size: u16, spacing: Spacing) -> u16 {
613 match spacing {
614 Spacing::Space(n) => pos.saturating_add(size).saturating_add(n),
615 Spacing::Overlap(n) => pos.saturating_add(size).saturating_sub(n),
616 }
617}
618
619const fn spacer_span(pos: u16, size: u16, spacing: Spacing) -> (u16, u16) {
624 let n = spacing.cells();
625 let start = match spacing {
626 Spacing::Space(_) => pos.saturating_add(size),
627 Spacing::Overlap(_) => pos.saturating_add(size).saturating_sub(n),
628 };
629 (start, n)
630}
631
632#[must_use]
660pub fn split_h_n_spaced<const N: usize>(
661 area: Rect,
662 constraints: [Constraint; N],
663 spacing: impl Into<Spacing>,
664) -> ([Rect; N], Vec<Rect>) {
665 let spacing = spacing.into();
666 if spacing.cells() == 0 || N < 2 {
667 return (split_h_n(area, constraints), Vec::new());
668 }
669 let sizes = solve_n(spaced_total(area.width(), N, spacing), &constraints);
670 let mut x = area.left();
671 let mut gaps = Vec::with_capacity(N.saturating_sub(1));
672 let panes = core::array::from_fn(|i| {
673 let w = sizes[i];
674 let rect = Rect::new(x, area.top(), w, area.height());
675 if i + 1 < N {
676 let (gx, gw) = spacer_span(x, w, spacing);
677 gaps.push(Rect::new(gx, area.top(), gw, area.height()));
678 }
679 x = step(x, w, spacing);
680 rect
681 });
682 (panes, gaps)
683}
684
685#[must_use]
691pub fn split_v_n_spaced<const N: usize>(
692 area: Rect,
693 constraints: [Constraint; N],
694 spacing: impl Into<Spacing>,
695) -> ([Rect; N], Vec<Rect>) {
696 let spacing = spacing.into();
697 if spacing.cells() == 0 || N < 2 {
698 return (split_v_n(area, constraints), Vec::new());
699 }
700 let sizes = solve_n(spaced_total(area.height(), N, spacing), &constraints);
701 let mut y = area.top();
702 let mut gaps = Vec::with_capacity(N.saturating_sub(1));
703 let panes = core::array::from_fn(|i| {
704 let h = sizes[i];
705 let rect = Rect::new(area.left(), y, area.width(), h);
706 if i + 1 < N {
707 let (gy, gh) = spacer_span(y, h, spacing);
708 gaps.push(Rect::new(area.left(), gy, area.width(), gh));
709 }
710 y = step(y, h, spacing);
711 rect
712 });
713 (panes, gaps)
714}
715
716#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
726#[non_exhaustive]
727pub enum Flex {
728 #[default]
731 Start,
732 End,
735 Center,
737 SpaceBetween,
740 SpaceAround,
743}
744
745fn place(total: u16, sizes: &[u16], flex: Flex) -> SmallBuf<u16, STACK_CAP> {
752 let content: u16 = sizes.iter().fold(0u16, |a, &b| a.saturating_add(b));
753 let slack = total.saturating_sub(content);
754 let n = sizes.len();
755 let mut offsets: SmallBuf<u16, STACK_CAP> = SmallBuf::with_capacity(n);
756
757 let packed_from = |start: u16, offsets: &mut SmallBuf<u16, STACK_CAP>| {
758 let mut pos = start;
759 for &s in sizes {
760 offsets.push(pos);
761 pos = pos.saturating_add(s);
762 }
763 };
764
765 match flex {
766 Flex::End => packed_from(slack, &mut offsets),
767 Flex::Center => packed_from(slack / 2, &mut offsets),
768 Flex::SpaceBetween if n > 1 => {
769 #[allow(clippy::cast_possible_truncation)]
772 let gaps = n as u16 - 1;
773 let gap = slack / gaps;
774 let mut extra = slack % gaps;
775 let mut pos = 0;
776 for (i, &s) in sizes.iter().enumerate() {
777 offsets.push(pos);
778 pos = pos.saturating_add(s);
779 if i + 1 < n {
780 pos = pos.saturating_add(gap + u16::from(extra > 0));
781 extra = extra.saturating_sub(1);
782 }
783 }
784 }
785 Flex::Start | Flex::SpaceBetween => packed_from(0, &mut offsets),
786 Flex::SpaceAround => {
787 #[allow(clippy::cast_possible_truncation)]
790 let gaps = n as u16 + 1;
791 let unit = slack / gaps;
792 let mut extra = slack % gaps;
793 let mut pos = unit + u16::from(extra > 0);
794 extra = extra.saturating_sub(u16::from(extra > 0));
795 for &s in sizes {
796 offsets.push(pos);
797 pos = pos.saturating_add(s);
798 pos = pos.saturating_add(unit + u16::from(extra > 0));
799 extra = extra.saturating_sub(u16::from(extra > 0));
800 }
801 }
802 }
803
804 offsets
805}
806
807fn place_n<const N: usize>(total: u16, sizes: &[u16; N], flex: Flex) -> [u16; N] {
810 let content: u16 = sizes.iter().fold(0u16, |a, &b| a.saturating_add(b));
811 let slack = total.saturating_sub(content);
812 let mut offsets = [0u16; N];
813
814 let packed_from = |start: u16, offsets: &mut [u16; N]| {
815 let mut pos = start;
816 for (o, &s) in offsets.iter_mut().zip(sizes.iter()) {
817 *o = pos;
818 pos = pos.saturating_add(s);
819 }
820 };
821
822 match flex {
823 Flex::End => packed_from(slack, &mut offsets),
824 Flex::Center => packed_from(slack / 2, &mut offsets),
825 Flex::SpaceBetween if N > 1 => {
826 #[allow(clippy::cast_possible_truncation)]
829 let gaps = N as u16 - 1;
830 let gap = slack / gaps;
831 let mut extra = slack % gaps;
832 let mut pos = 0;
833 for (i, &s) in sizes.iter().enumerate() {
834 offsets[i] = pos;
835 pos = pos.saturating_add(s);
836 if i + 1 < N {
837 pos = pos.saturating_add(gap + u16::from(extra > 0));
838 extra = extra.saturating_sub(1);
839 }
840 }
841 }
842 Flex::Start | Flex::SpaceBetween => packed_from(0, &mut offsets),
843 Flex::SpaceAround => {
844 #[allow(clippy::cast_possible_truncation)]
847 let gaps = N as u16 + 1;
848 let unit = slack / gaps;
849 let mut extra = slack % gaps;
850 let mut pos = unit + u16::from(extra > 0);
851 extra = extra.saturating_sub(u16::from(extra > 0));
852 for (i, &s) in sizes.iter().enumerate() {
853 offsets[i] = pos;
854 pos = pos.saturating_add(s);
855 pos = pos.saturating_add(unit + u16::from(extra > 0));
856 extra = extra.saturating_sub(u16::from(extra > 0));
857 }
858 }
859 }
860
861 offsets
862}
863
864#[must_use]
870pub fn split_v_flex(area: Rect, constraints: &[Constraint], flex: Flex) -> Vec<Rect> {
871 let sizes = solve(area.height(), constraints);
872 let offsets = place(area.height(), &sizes, flex);
873 offsets
874 .iter()
875 .copied()
876 .zip(sizes.iter().copied())
877 .map(|(y, h)| Rect::new(area.left(), area.top().saturating_add(y), area.width(), h))
878 .collect()
879}
880
881#[must_use]
886pub fn split_v_n_flex<const N: usize>(
887 area: Rect,
888 constraints: [Constraint; N],
889 flex: Flex,
890) -> [Rect; N] {
891 let sizes = solve_n(area.height(), &constraints);
892 let offsets = place_n(area.height(), &sizes, flex);
893 core::array::from_fn(|i| {
894 Rect::new(
895 area.left(),
896 area.top().saturating_add(offsets[i]),
897 area.width(),
898 sizes[i],
899 )
900 })
901}
902
903#[must_use]
909pub fn split_h_flex(area: Rect, constraints: &[Constraint], flex: Flex) -> Vec<Rect> {
910 let sizes = solve(area.width(), constraints);
911 let offsets = place(area.width(), &sizes, flex);
912 offsets
913 .iter()
914 .copied()
915 .zip(sizes.iter().copied())
916 .map(|(x, w)| Rect::new(area.left().saturating_add(x), area.top(), w, area.height()))
917 .collect()
918}
919
920#[must_use]
925pub fn split_h_n_flex<const N: usize>(
926 area: Rect,
927 constraints: [Constraint; N],
928 flex: Flex,
929) -> [Rect; N] {
930 let sizes = solve_n(area.width(), &constraints);
931 let offsets = place_n(area.width(), &sizes, flex);
932 core::array::from_fn(|i| {
933 Rect::new(
934 area.left().saturating_add(offsets[i]),
935 area.top(),
936 sizes[i],
937 area.height(),
938 )
939 })
940}
941
942#[must_use]
955pub fn centered_rect(screen: Rect, width: u16, height: u16) -> Rect {
956 let width = width.min(screen.width());
957 let height = height.min(screen.height());
958 let x = screen.left().saturating_add((screen.width() - width) / 2);
959 let y = screen.top().saturating_add((screen.height() - height) / 2);
960 Rect::new(x, y, width, height)
961}
962
963#[derive(Clone, Copy, Debug, PartialEq, Eq)]
965pub enum Side {
966 Above,
968 Below,
971 Left,
973 Right,
975}
976
977impl Side {
978 const fn opposite(self) -> Self {
980 match self {
981 Self::Above => Self::Below,
982 Self::Below => Self::Above,
983 Self::Left => Self::Right,
984 Self::Right => Self::Left,
985 }
986 }
987}
988
989#[must_use]
1027pub fn anchored_rect(anchor: Rect, size: Size, preferred: Side, bounds: Rect) -> Rect {
1028 let width = size.width().min(bounds.width());
1029 let height = size.height().min(bounds.height());
1030
1031 let fits = |candidate: Side| match candidate {
1034 Side::Above => {
1035 anchor.top() <= bounds.bottom()
1036 && anchor
1037 .top()
1038 .checked_sub(height)
1039 .is_some_and(|t| t >= bounds.top())
1040 }
1041 Side::Below => anchor.bottom().saturating_add(height) <= bounds.bottom(),
1042 Side::Left => {
1043 anchor.left() <= bounds.right()
1044 && anchor
1045 .left()
1046 .checked_sub(width)
1047 .is_some_and(|l| l >= bounds.left())
1048 }
1049 Side::Right => anchor.right().saturating_add(width) <= bounds.right(),
1050 };
1051 let resolved = if fits(preferred) || !fits(preferred.opposite()) {
1052 preferred
1053 } else {
1054 preferred.opposite()
1055 };
1056
1057 let (x, y) = match resolved {
1058 Side::Above => {
1059 let x = anchor
1060 .left()
1061 .min(bounds.right().saturating_sub(width))
1062 .max(bounds.left());
1063 let y = anchor
1064 .top()
1065 .saturating_sub(height)
1066 .min(bounds.bottom().saturating_sub(height))
1067 .max(bounds.top());
1068 (x, y)
1069 }
1070 Side::Below => {
1071 let x = anchor
1072 .left()
1073 .min(bounds.right().saturating_sub(width))
1074 .max(bounds.left());
1075 let y = anchor
1076 .bottom()
1077 .min(bounds.bottom().saturating_sub(height))
1078 .max(bounds.top());
1079 (x, y)
1080 }
1081 Side::Left => {
1082 let x = anchor
1083 .left()
1084 .saturating_sub(width)
1085 .min(bounds.right().saturating_sub(width))
1086 .max(bounds.left());
1087 let y = anchor
1088 .top()
1089 .min(bounds.bottom().saturating_sub(height))
1090 .max(bounds.top());
1091 (x, y)
1092 }
1093 Side::Right => {
1094 let x = anchor
1095 .right()
1096 .min(bounds.right().saturating_sub(width))
1097 .max(bounds.left());
1098 let y = anchor
1099 .top()
1100 .min(bounds.bottom().saturating_sub(height))
1101 .max(bounds.top());
1102 (x, y)
1103 }
1104 };
1105
1106 Rect::new(x, y, width, height)
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use alloc::vec;
1112
1113 use super::*;
1114
1115 #[test]
1116 fn vertical_split_sums_and_clamps() {
1117 let area = Rect::new(0, 0, 20, 10);
1118 let panes = split_v(
1119 area,
1120 &[
1121 Constraint::Fixed(1),
1122 Constraint::Fill(1),
1123 Constraint::Fixed(1),
1124 ],
1125 );
1126 assert_eq!(panes.len(), 3);
1127 assert_eq!(panes[0].height(), 1);
1129 assert_eq!(panes[1].height(), 8);
1130 assert_eq!(panes[2].height(), 1);
1131 assert_eq!(panes[0].top(), 0);
1133 assert_eq!(panes[1].top(), 1);
1134 assert_eq!(panes[2].top(), 9);
1135 assert_eq!(panes[2].bottom(), area.bottom());
1136 for p in &panes {
1138 assert_eq!(p.width(), 20);
1139 }
1140 }
1141
1142 #[test]
1143 fn horizontal_percent_and_fill() {
1144 let area = Rect::new(0, 0, 100, 5);
1145 let panes = split_h(area, &[Constraint::Percent(30), Constraint::Fill(1)]);
1146 assert_eq!(panes[0].width(), 30);
1147 assert_eq!(panes[1].width(), 70);
1148 assert_eq!(panes[0].left(), 0);
1149 assert_eq!(panes[1].left(), 30);
1150 assert_eq!(panes[1].right(), area.right());
1151 }
1152
1153 #[test]
1154 fn horizontal_ratio_and_fill() {
1155 let area = Rect::new(0, 0, 100, 5);
1156 let panes = split_h(area, &[Constraint::Ratio(3, 10), Constraint::Fill(1)]);
1157 assert_eq!(panes[0].width(), 30);
1158 assert_eq!(panes[1].width(), 70);
1159 }
1160
1161 #[test]
1162 fn ratio_zero_denominator_resolves_to_zero() {
1163 let area = Rect::new(0, 0, 100, 5);
1164 let panes = split_h(area, &[Constraint::Ratio(1, 0), Constraint::Fill(1)]);
1165 assert_eq!(panes[0].width(), 0);
1166 assert_eq!(panes[1].width(), 100);
1167 }
1168
1169 #[test]
1170 fn ratio_over_one_clamps_to_total() {
1171 let area = Rect::new(0, 0, 100, 5);
1172 let panes = split_h(area, &[Constraint::Ratio(3, 2)]);
1173 assert_eq!(panes[0].width(), 100);
1174 }
1175
1176 #[test]
1177 fn fill_remainder_distributes_evenly() {
1178 let area = Rect::new(0, 0, 10, 1);
1179 let panes = split_h(
1181 area,
1182 &[
1183 Constraint::Fill(1),
1184 Constraint::Fill(1),
1185 Constraint::Fill(1),
1186 ],
1187 );
1188 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1189 assert_eq!(widths, vec![4, 3, 3]);
1190 assert_eq!(widths.iter().sum::<u16>(), 10);
1191 }
1192
1193 #[test]
1194 fn oversized_fixed_is_clamped() {
1195 let area = Rect::new(0, 0, 5, 3);
1196 let panes = split_h(area, &[Constraint::Fixed(10), Constraint::Fixed(10)]);
1198 assert_eq!(panes[0].width(), 5);
1199 assert_eq!(panes[1].width(), 0);
1200 for p in &panes {
1202 assert!(p.right() <= area.right());
1203 }
1204 }
1205
1206 #[test]
1207 fn no_fill_leaves_gap() {
1208 let area = Rect::new(0, 0, 10, 4);
1209 let panes = split_v(area, &[Constraint::Fixed(2), Constraint::Fixed(2)]);
1210 assert_eq!(panes[0].height(), 2);
1212 assert_eq!(panes[1].height(), 2);
1213 assert_eq!(panes[1].bottom(), 4);
1214 }
1215
1216 #[test]
1217 fn min_gets_at_least_its_floor_plus_a_share() {
1218 let area = Rect::new(0, 0, 10, 1);
1219 let panes = split_h(area, &[Constraint::Min(3), Constraint::Fill(1)]);
1224 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1225 assert_eq!(widths, vec![7, 3]);
1226 assert_eq!(widths.iter().sum::<u16>(), 10);
1227 }
1228
1229 #[test]
1230 fn min_floor_holds_when_share_would_be_smaller() {
1231 let area = Rect::new(0, 0, 10, 1);
1232 let panes = split_h(
1237 area,
1238 &[Constraint::Min(4), Constraint::Fill(1), Constraint::Fill(1)],
1239 );
1240 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1241 assert_eq!(widths[0], 6);
1242 assert_eq!(widths[1], 2);
1243 assert_eq!(widths[2], 2);
1244 assert_eq!(widths.iter().sum::<u16>(), 10);
1245 }
1246
1247 #[test]
1248 fn max_caps_its_share_and_leaves_the_rest_unclaimed() {
1249 let area = Rect::new(0, 0, 10, 1);
1250 let panes = split_h(area, &[Constraint::Fill(1), Constraint::Max(2)]);
1253 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1254 assert_eq!(widths, vec![5, 2]);
1255 assert_eq!(widths.iter().sum::<u16>(), 7);
1256 }
1257
1258 #[test]
1259 fn weighted_fill_splits_proportionally() {
1260 let area = Rect::new(0, 0, 12, 1);
1261 let panes = split_h(area, &[Constraint::Fill(1), Constraint::Fill(2)]);
1263 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1264 assert_eq!(widths, vec![4, 8]);
1265 assert_eq!(widths.iter().sum::<u16>(), 12);
1266 }
1267
1268 #[test]
1269 fn weighted_fill_at_weight_one_matches_equal_distribution() {
1270 let area = Rect::new(0, 0, 10, 1);
1271 let panes = split_h(
1274 area,
1275 &[
1276 Constraint::Fill(5),
1277 Constraint::Fill(5),
1278 Constraint::Fill(5),
1279 ],
1280 );
1281 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1282 assert_eq!(widths, vec![4, 3, 3]);
1283 assert_eq!(widths.iter().sum::<u16>(), 10);
1284 }
1285
1286 #[test]
1287 fn weighted_fill_leftover_goes_to_the_largest_fractional_share() {
1288 let area = Rect::new(0, 0, 10, 1);
1289 let panes = split_h(
1294 area,
1295 &[
1296 Constraint::Fill(3),
1297 Constraint::Fill(2),
1298 Constraint::Fill(2),
1299 ],
1300 );
1301 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1302 assert_eq!(widths, vec![4, 3, 3]);
1303 assert_eq!(widths.iter().sum::<u16>(), 10);
1304 }
1305
1306 #[test]
1307 fn fill_weight_zero_claims_no_share_of_the_remainder() {
1308 let area = Rect::new(0, 0, 10, 1);
1309 let panes = split_h(area, &[Constraint::Fill(0), Constraint::Fill(1)]);
1310 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1311 assert_eq!(widths, vec![0, 10]);
1312 }
1313
1314 #[test]
1315 fn all_fill_weights_zero_leaves_the_remainder_unclaimed() {
1316 let area = Rect::new(0, 0, 10, 1);
1317 let panes = split_h(area, &[Constraint::Fill(0), Constraint::Fill(0)]);
1318 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1319 assert_eq!(widths, vec![0, 0]);
1320 }
1321
1322 #[test]
1323 fn weighted_fill_mixes_with_min_and_max_at_weight_one() {
1324 let area = Rect::new(0, 0, 20, 1);
1325 let panes = split_h(
1330 area,
1331 &[
1332 Constraint::Fill(3),
1333 Constraint::Min(2),
1334 Constraint::Fill(1),
1335 Constraint::Max(10),
1336 ],
1337 );
1338 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1339 assert_eq!(widths, vec![9, 5, 3, 3]);
1340 assert_eq!(widths.iter().sum::<u16>(), 20);
1341 }
1342
1343 #[test]
1344 fn flex_start_matches_split_v() {
1345 let area = Rect::new(0, 0, 10, 4);
1346 let constraints = [Constraint::Fixed(2), Constraint::Fixed(2)];
1347 let legacy = split_v(area, &constraints);
1348 let flexed = split_v_flex(area, &constraints, Flex::Start);
1349 assert_eq!(legacy, flexed);
1350 }
1351
1352 #[test]
1353 fn flex_end_pushes_leftover_before_the_panes() {
1354 let area = Rect::new(0, 0, 10, 10);
1355 let panes = split_v_flex(
1356 area,
1357 &[Constraint::Fixed(2), Constraint::Fixed(2)],
1358 Flex::End,
1359 );
1360 assert_eq!(panes[0].top(), 6);
1362 assert_eq!(panes[1].top(), 8);
1363 assert_eq!(panes[1].bottom(), 10);
1364 }
1365
1366 #[test]
1367 fn flex_center_splits_leftover_around_the_panes() {
1368 let area = Rect::new(0, 0, 10, 10);
1369 let panes = split_v_flex(area, &[Constraint::Fixed(4)], Flex::Center);
1370 assert_eq!(panes[0].top(), 3);
1372 assert_eq!(panes[0].bottom(), 7);
1373 }
1374
1375 #[test]
1376 fn flex_space_between_puts_leftover_between_panes_only() {
1377 let area = Rect::new(0, 0, 10, 1);
1378 let panes = split_h_flex(
1379 area,
1380 &[Constraint::Fixed(2), Constraint::Fixed(2)],
1381 Flex::SpaceBetween,
1382 );
1383 assert_eq!(panes[0].left(), 0);
1385 assert_eq!(panes[0].right(), 2);
1386 assert_eq!(panes[1].left(), 8);
1387 assert_eq!(panes[1].right(), 10);
1388 }
1389
1390 #[test]
1391 fn flex_space_around_puts_equal_gaps_at_both_edges() {
1392 let area = Rect::new(0, 0, 9, 1);
1393 let panes = split_h_flex(area, &[Constraint::Fixed(3)], Flex::SpaceAround);
1394 assert_eq!(panes[0].left(), 3);
1396 assert_eq!(panes[0].right(), 6);
1397 }
1398
1399 #[test]
1400 fn spaced_split_carves_out_gaps_between_panes() {
1401 let area = Rect::new(0, 0, 59, 6);
1402 let (panes, gaps) = split_h_spaced(
1403 area,
1404 &[
1405 Constraint::Fill(1),
1406 Constraint::Fill(1),
1407 Constraint::Fill(1),
1408 ],
1409 1,
1410 );
1411 assert_eq!(panes.len(), 3);
1412 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1413 assert_eq!(widths, vec![19, 19, 19]);
1414 assert_eq!(panes[1].left(), panes[0].right() + 1);
1416 assert_eq!(panes[2].left(), panes[1].right() + 1);
1417 assert_eq!(gaps.len(), 2);
1419 assert_eq!(
1420 gaps[0],
1421 Rect::new(panes[0].right(), area.top(), 1, area.height())
1422 );
1423 assert_eq!(
1424 gaps[1],
1425 Rect::new(panes[1].right(), area.top(), 1, area.height())
1426 );
1427 }
1428
1429 #[test]
1430 fn spaced_split_resolves_percent_against_the_post_gap_axis() {
1431 let area = Rect::new(0, 0, 100, 1);
1432 let (panes, _) = split_h_spaced(
1433 area,
1434 &[Constraint::Percent(50), Constraint::Percent(50)],
1435 10,
1436 );
1437 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1438 assert_eq!(widths, vec![45, 45]);
1439
1440 let (panes, _) = split_h_spaced(
1441 area,
1442 &[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
1443 10,
1444 );
1445 let widths: Vec<u16> = panes.iter().map(Rect::width).collect();
1446 assert_eq!(widths, vec![45, 45]);
1447 }
1448
1449 #[test]
1450 fn spaced_split_falls_back_with_one_pane_or_no_spacing() {
1451 let area = Rect::new(0, 0, 10, 1);
1452 assert_eq!(
1453 split_h_spaced(area, &[Constraint::Fill(1)], 1),
1454 (split_h(area, &[Constraint::Fill(1)]), Vec::new())
1455 );
1456 assert_eq!(
1457 split_h_spaced(area, &[Constraint::Fill(1), Constraint::Fill(1)], 0),
1458 (
1459 split_h(area, &[Constraint::Fill(1), Constraint::Fill(1)]),
1460 Vec::new()
1461 )
1462 );
1463 }
1464
1465 #[test]
1466 fn vertical_spaced_split_matches_horizontal_shape() {
1467 let area = Rect::new(0, 0, 6, 59);
1468 let (panes, gaps) = split_v_spaced(
1469 area,
1470 &[
1471 Constraint::Fill(1),
1472 Constraint::Fill(1),
1473 Constraint::Fill(1),
1474 ],
1475 1,
1476 );
1477 let heights: Vec<u16> = panes.iter().map(Rect::height).collect();
1478 assert_eq!(heights, vec![19, 19, 19]);
1479 assert_eq!(panes[1].top(), panes[0].bottom() + 1);
1480 assert_eq!(gaps.len(), 2);
1481 assert_eq!(
1482 gaps[0],
1483 Rect::new(area.left(), panes[0].bottom(), area.width(), 1)
1484 );
1485 }
1486
1487 #[test]
1488 fn overlap_spacing_shares_one_edge_cell_between_panes() {
1489 let area = Rect::new(0, 0, 60, 6);
1490 let (panes, gaps) = split_h_spaced(
1491 area,
1492 &[Constraint::Fill(1), Constraint::Fill(1)],
1493 Spacing::Overlap(1),
1494 );
1495 assert_eq!(panes.len(), 2);
1496 assert_eq!(panes[0].left(), area.left());
1498 assert_eq!(panes[1].right(), area.right());
1499 assert_eq!(panes[1].left(), panes[0].right() - 1);
1500 assert_eq!(gaps.len(), 1);
1502 assert_eq!(
1503 gaps[0],
1504 Rect::new(panes[0].right() - 1, area.top(), 1, area.height())
1505 );
1506 }
1507
1508 #[test]
1509 fn overlap_spacing_works_vertically_and_with_more_than_two_panes() {
1510 let area = Rect::new(0, 0, 6, 61);
1511 let (panes, gaps) = split_v_spaced(area, &[Constraint::Fill(1); 3], Spacing::Overlap(1));
1512 assert_eq!(panes.len(), 3);
1513 assert_eq!(panes[0].top(), area.top());
1514 assert_eq!(panes[2].bottom(), area.bottom());
1515 assert_eq!(panes[1].top(), panes[0].bottom() - 1);
1516 assert_eq!(panes[2].top(), panes[1].bottom() - 1);
1517 assert_eq!(gaps.len(), 2);
1518 }
1519
1520 #[test]
1521 fn overlap_spacing_falls_back_with_one_pane_or_zero_overlap() {
1522 let area = Rect::new(0, 0, 10, 1);
1523 assert_eq!(
1524 split_h_spaced(area, &[Constraint::Fill(1)], Spacing::Overlap(1)),
1525 (split_h(area, &[Constraint::Fill(1)]), Vec::new())
1526 );
1527 assert_eq!(
1528 split_h_spaced(
1529 area,
1530 &[Constraint::Fill(1), Constraint::Fill(1)],
1531 Spacing::Overlap(0)
1532 ),
1533 (
1534 split_h(area, &[Constraint::Fill(1), Constraint::Fill(1)]),
1535 Vec::new()
1536 )
1537 );
1538 }
1539
1540 #[test]
1541 fn split_h_n_spaced_matches_split_h_spaced_with_overlap() {
1542 let area = Rect::new(0, 0, 60, 6);
1543 let constraints = [Constraint::Fill(1); 2];
1544 let (vec_panes, vec_gaps) = split_h_spaced(area, &constraints, Spacing::Overlap(1));
1545 let ([a, b], gaps) = split_h_n_spaced(area, constraints, Spacing::Overlap(1));
1546 assert_eq!(vec_panes, vec![a, b]);
1547 assert_eq!(vec_gaps, gaps);
1548 }
1549
1550 #[test]
1551 fn centered_rect_centers_within_the_screen() {
1552 let screen = Rect::new(0, 0, 20, 10);
1553 let r = centered_rect(screen, 10, 4);
1554 assert_eq!(r, Rect::new(5, 3, 10, 4));
1555 }
1556
1557 #[test]
1558 fn centered_rect_clamps_to_the_screen_size_when_larger() {
1559 let screen = Rect::new(0, 0, 20, 10);
1560 let r = centered_rect(screen, 100, 100);
1561 assert_eq!(r, Rect::new(0, 0, 20, 10));
1562 }
1563
1564 #[test]
1565 fn centered_rect_respects_a_non_origin_screen() {
1566 let screen = Rect::new(5, 5, 20, 10);
1567 let r = centered_rect(screen, 10, 4);
1568 assert_eq!(r, Rect::new(10, 8, 10, 4));
1569 }
1570
1571 #[test]
1572 fn centered_rect_does_not_overflow_on_a_far_off_screen() {
1573 let screen = Rect::new(50_000, 0, 40_000, 10);
1576 let r = centered_rect(screen, 10, 4);
1577 assert_eq!(r, Rect::new(u16::MAX, 3, 10, 4));
1578 }
1579
1580 #[test]
1581 fn anchored_rect_opens_below_when_preferred() {
1582 let bounds = Rect::new(0, 0, 40, 20);
1583 let anchor = Rect::new(5, 5, 10, 1);
1584 let r = anchored_rect(anchor, Size::new(12, 4), Side::Below, bounds);
1585 assert_eq!(r, Rect::new(5, 6, 12, 4));
1586 }
1587
1588 #[test]
1589 fn anchored_rect_opens_above_when_preferred() {
1590 let bounds = Rect::new(0, 0, 40, 20);
1591 let anchor = Rect::new(5, 10, 10, 1);
1592 let r = anchored_rect(anchor, Size::new(12, 4), Side::Above, bounds);
1593 assert_eq!(r, Rect::new(5, 6, 12, 4));
1594 }
1595
1596 #[test]
1597 fn anchored_rect_flips_below_to_above_when_there_is_no_room_below() {
1598 let bounds = Rect::new(0, 0, 40, 20);
1599 let anchor = Rect::new(5, 18, 10, 1);
1601 let r = anchored_rect(anchor, Size::new(12, 4), Side::Below, bounds);
1602 assert_eq!(r, Rect::new(5, 14, 12, 4));
1603 }
1604
1605 #[test]
1606 fn anchored_rect_flips_above_to_below_when_there_is_no_room_above() {
1607 let bounds = Rect::new(0, 0, 40, 20);
1608 let anchor = Rect::new(5, 1, 10, 1);
1610 let r = anchored_rect(anchor, Size::new(12, 4), Side::Above, bounds);
1611 assert_eq!(r, Rect::new(5, 2, 12, 4));
1612 }
1613
1614 #[test]
1615 fn anchored_rect_keeps_preferred_side_when_neither_side_has_room() {
1616 let bounds = Rect::new(0, 0, 40, 3);
1617 let anchor = Rect::new(5, 1, 10, 1);
1618 let r = anchored_rect(anchor, Size::new(12, 4), Side::Below, bounds);
1622 assert_eq!(r, Rect::new(5, 0, 12, 3));
1623 }
1624
1625 #[test]
1626 fn anchored_rect_clamps_to_the_right_bounds_edge() {
1627 let bounds = Rect::new(0, 0, 20, 20);
1628 let anchor = Rect::new(15, 5, 4, 1);
1631 let r = anchored_rect(anchor, Size::new(12, 4), Side::Below, bounds);
1632 assert_eq!(r, Rect::new(8, 6, 12, 4));
1633 }
1634
1635 #[test]
1636 fn anchored_rect_clamps_to_the_left_bounds_edge() {
1637 let bounds = Rect::new(5, 0, 20, 20);
1639 let anchor = Rect::new(2, 5, 2, 1);
1640 let r = anchored_rect(anchor, Size::new(12, 4), Side::Below, bounds);
1641 assert_eq!(r, Rect::new(5, 6, 12, 4));
1642 }
1643
1644 #[test]
1645 fn anchored_rect_opens_to_the_right_and_clamps_vertically() {
1646 let bounds = Rect::new(0, 0, 40, 10);
1647 let anchor = Rect::new(5, 8, 6, 1);
1650 let r = anchored_rect(anchor, Size::new(8, 6), Side::Right, bounds);
1651 assert_eq!(r, Rect::new(11, 4, 8, 6));
1652 }
1653
1654 #[test]
1655 fn anchored_rect_opens_to_the_left() {
1656 let bounds = Rect::new(0, 0, 40, 10);
1657 let anchor = Rect::new(20, 2, 6, 1);
1658 let r = anchored_rect(anchor, Size::new(8, 4), Side::Left, bounds);
1659 assert_eq!(r, Rect::new(12, 2, 8, 4));
1660 }
1661
1662 #[test]
1663 fn anchored_rect_above_stays_within_bounds_for_an_anchor_below_them() {
1664 let bounds = Rect::new(0, 0, 40, 10);
1665 let anchor = Rect::new(5, 50, 10, 1);
1667 let r = anchored_rect(anchor, Size::new(12, 4), Side::Above, bounds);
1668 assert!(r.top() >= bounds.top() && r.bottom() <= bounds.bottom());
1669 }
1670
1671 #[test]
1672 fn anchored_rect_left_stays_within_bounds_for_an_anchor_right_of_them() {
1673 let bounds = Rect::new(0, 0, 10, 10);
1674 let anchor = Rect::new(50, 2, 2, 1);
1676 let r = anchored_rect(anchor, Size::new(4, 2), Side::Left, bounds);
1677 assert!(r.left() >= bounds.left() && r.right() <= bounds.right());
1678 }
1679
1680 #[test]
1681 fn anchored_rect_clamps_size_down_to_bounds() {
1682 let bounds = Rect::new(0, 0, 10, 10);
1683 let anchor = Rect::new(2, 2, 2, 1);
1684 let r = anchored_rect(anchor, Size::new(100, 100), Side::Below, bounds);
1685 assert_eq!(r.width(), 10);
1686 assert_eq!(r.height(), 10);
1687 assert!(r.left() >= bounds.left() && r.right() <= bounds.right());
1688 assert!(r.top() >= bounds.top() && r.bottom() <= bounds.bottom());
1689 }
1690
1691 #[test]
1692 fn anchored_rect_handles_a_zero_size_bounds() {
1693 let bounds = Rect::new(3, 3, 0, 0);
1694 let anchor = Rect::new(3, 3, 0, 0);
1695 let r = anchored_rect(anchor, Size::new(5, 5), Side::Below, bounds);
1696 assert_eq!(r, Rect::new(3, 3, 0, 0));
1697 }
1698
1699 #[test]
1705 fn split_beyond_stack_cap_matches_small_case_behavior() {
1706 let panes = 20; #[allow(clippy::cast_possible_truncation)]
1708 let panes_u16 = panes as u16;
1709 let area = Rect::new(0, 0, panes_u16, 1);
1710 let constraints = vec![Constraint::Fixed(1); panes];
1711 let widths: Vec<u16> = split_h(area, &constraints)
1712 .iter()
1713 .map(Rect::width)
1714 .collect();
1715 assert_eq!(widths, vec![1u16; panes]);
1716 assert_eq!(widths.iter().sum::<u16>(), panes_u16);
1717 }
1718
1719 #[test]
1723 fn weighted_fill_beyond_stack_cap_matches_small_case_proportions() {
1724 let area = Rect::new(0, 0, 100, 1);
1725 let constraints = vec![Constraint::Fill(1); 20];
1728 let widths: Vec<u16> = split_h(area, &constraints)
1729 .iter()
1730 .map(Rect::width)
1731 .collect();
1732 assert_eq!(widths.len(), 20);
1733 assert_eq!(widths.iter().sum::<u16>(), 100);
1734 assert!(widths.iter().all(|&w| w == 5));
1736 }
1737
1738 #[test]
1739 fn split_v_n_matches_split_v() {
1740 let area = Rect::new(0, 0, 20, 10);
1741 let constraints = [
1742 Constraint::Fixed(1),
1743 Constraint::Fill(1),
1744 Constraint::Fixed(1),
1745 ];
1746 let vec_panes = split_v(area, &constraints);
1747 let [a, b, c] = split_v_n(area, constraints);
1748 assert_eq!(vec_panes, vec![a, b, c]);
1749 }
1750
1751 #[test]
1752 fn split_h_n_matches_split_h() {
1753 let area = Rect::new(0, 0, 100, 5);
1754 let constraints = [Constraint::Percent(30), Constraint::Fill(1)];
1755 let vec_panes = split_h(area, &constraints);
1756 let [left, right] = split_h_n(area, constraints);
1757 assert_eq!(vec_panes, vec![left, right]);
1758 }
1759
1760 #[test]
1761 fn split_v_n_destructures_by_compile_time_count() {
1762 let area = Rect::new(0, 0, 12, 12);
1763 let [header, body, footer] = split_v_n(
1764 area,
1765 [
1766 Constraint::Fixed(2),
1767 Constraint::Fill(1),
1768 Constraint::Fixed(2),
1769 ],
1770 );
1771 assert_eq!(header.height(), 2);
1772 assert_eq!(body.height(), 8);
1773 assert_eq!(footer.height(), 2);
1774 assert_eq!(header.top(), 0);
1775 assert_eq!(body.top(), 2);
1776 assert_eq!(footer.top(), 10);
1777 }
1778
1779 #[test]
1780 fn split_h_n_flex_matches_split_h_flex() {
1781 let area = Rect::new(0, 0, 10, 1);
1782 let constraints = [Constraint::Fixed(2), Constraint::Fixed(2)];
1783 let vec_panes = split_h_flex(area, &constraints, Flex::SpaceBetween);
1784 let [a, b] = split_h_n_flex(area, constraints, Flex::SpaceBetween);
1785 assert_eq!(vec_panes, vec![a, b]);
1786 }
1787
1788 #[test]
1789 fn split_v_n_flex_matches_split_v_flex() {
1790 let area = Rect::new(0, 0, 10, 10);
1791 let constraints = [Constraint::Fixed(2), Constraint::Fixed(2)];
1792 let vec_panes = split_v_flex(area, &constraints, Flex::End);
1793 let [a, b] = split_v_n_flex(area, constraints, Flex::End);
1794 assert_eq!(vec_panes, vec![a, b]);
1795 }
1796
1797 #[test]
1798 fn split_h_n_spaced_matches_split_h_spaced() {
1799 let area = Rect::new(0, 0, 59, 6);
1800 let constraints = [Constraint::Fill(1); 3];
1801 let (vec_panes, vec_gaps) = split_h_spaced(area, &constraints, 1);
1802 let ([a, b, c], gaps) = split_h_n_spaced(area, constraints, 1);
1803 assert_eq!(vec_panes, vec![a, b, c]);
1804 assert_eq!(vec_gaps, gaps);
1805 }
1806
1807 #[test]
1808 fn split_v_n_spaced_matches_split_v_spaced() {
1809 let area = Rect::new(0, 0, 6, 59);
1810 let constraints = [Constraint::Fill(1); 3];
1811 let (vec_panes, vec_gaps) = split_v_spaced(area, &constraints, 1);
1812 let ([a, b, c], gaps) = split_v_n_spaced(area, constraints, 1);
1813 assert_eq!(vec_panes, vec![a, b, c]);
1814 assert_eq!(vec_gaps, gaps);
1815 }
1816
1817 #[test]
1818 fn split_h_n_spaced_falls_back_with_one_pane_or_no_spacing() {
1819 let area = Rect::new(0, 0, 10, 1);
1820 assert_eq!(
1821 split_h_n_spaced(area, [Constraint::Fill(1)], 1),
1822 ([split_h_n(area, [Constraint::Fill(1)])[0]], Vec::new())
1823 );
1824 let constraints = [Constraint::Fill(1), Constraint::Fill(1)];
1825 assert_eq!(
1826 split_h_n_spaced(area, constraints, 0),
1827 (split_h_n(area, constraints), Vec::new())
1828 );
1829 }
1830
1831 #[test]
1835 fn split_h_n_beyond_stack_cap_matches_split_h() {
1836 let area = Rect::new(0, 0, 20, 1);
1837 let constraints = [Constraint::Fixed(1); 20];
1838 let vec_panes = split_h(area, &constraints);
1839 let arr_panes = split_h_n(area, constraints);
1840 assert_eq!(vec_panes, arr_panes.to_vec());
1841 }
1842}