1use crate::geometry::{EdgeInsets, Rect};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum Axis {
5 Horizontal,
6 Vertical,
7}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Align {
11 Start,
12 Center,
13 End,
14 Stretch,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum JustifyContent {
19 Start,
20 Center,
21 End,
22 SpaceBetween,
23 SpaceAround,
24 SpaceEvenly,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct LinearLayout {
29 pub axis: Axis,
30 pub gap: u16,
31 pub padding: EdgeInsets,
32 pub cross_align: Align,
33 pub justify: JustifyContent,
34}
35
36impl LinearLayout {
37 pub const fn column() -> Self {
38 Self {
39 axis: Axis::Vertical,
40 gap: 2,
41 padding: EdgeInsets::all(0),
42 cross_align: Align::Stretch,
43 justify: JustifyContent::Start,
44 }
45 }
46
47 pub const fn row() -> Self {
48 Self {
49 axis: Axis::Horizontal,
50 gap: 2,
51 padding: EdgeInsets::all(0),
52 cross_align: Align::Stretch,
53 justify: JustifyContent::Start,
54 }
55 }
56
57 pub const fn flex_row() -> Self {
58 Self::row()
59 }
60
61 pub const fn flex_column() -> Self {
62 Self::column()
63 }
64
65 pub const fn with_gap(mut self, gap: u16) -> Self {
66 self.gap = gap;
67 self
68 }
69
70 pub const fn with_padding(mut self, padding: EdgeInsets) -> Self {
71 self.padding = padding;
72 self
73 }
74
75 pub const fn with_justify(mut self, justify: JustifyContent) -> Self {
76 self.justify = justify;
77 self
78 }
79
80 pub const fn with_cross_align(mut self, cross_align: Align) -> Self {
81 self.cross_align = cross_align;
82 self
83 }
84
85 pub fn arrange(&self, area: Rect, item_count: usize, out: &mut [Rect]) -> usize {
86 if item_count == 0 || out.is_empty() {
87 return 0;
88 }
89
90 let count = item_count.min(out.len());
91 let inner = area.inset(self.padding);
92 let gap_total = self.gap as u32 * count.saturating_sub(1) as u32;
93
94 match self.axis {
95 Axis::Vertical => {
96 let each_h = inner.h.saturating_sub(gap_total) / count as u32;
97 let mut y = inner.y;
98 for slot in out.iter_mut().take(count) {
99 *slot = Rect::new(inner.x, y, inner.w, each_h);
100 y += each_h as i32 + self.gap as i32;
101 }
102 }
103 Axis::Horizontal => {
104 let each_w = inner.w.saturating_sub(gap_total) / count as u32;
105 let mut x = inner.x;
106 for slot in out.iter_mut().take(count) {
107 *slot = Rect::new(x, inner.y, each_w, inner.h);
108 x += each_w as i32 + self.gap as i32;
109 }
110 }
111 }
112
113 count
114 }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub enum Constraint {
119 Min(u32),
121 Max(u32),
123 Length(u32),
125 Percent(u8),
127 Ratio(u32, u32),
129 Fill(u16),
131}
132
133impl Constraint {
134 pub const fn length(px: u32) -> Self {
135 Self::Length(px)
136 }
137
138 pub const fn min(px: u32) -> Self {
139 Self::Min(px)
140 }
141
142 pub const fn max(px: u32) -> Self {
143 Self::Max(px)
144 }
145
146 pub const fn percent(percent: u8) -> Self {
147 Self::Percent(percent)
148 }
149
150 pub const fn ratio(numerator: u32, denominator: u32) -> Self {
151 Self::Ratio(numerator, denominator)
152 }
153
154 pub const fn fill(weight: u16) -> Self {
155 Self::Fill(weight)
156 }
157
158 fn fixed_size(self, total: u32) -> Option<u32> {
159 match self {
160 Self::Length(px) | Self::Min(px) | Self::Max(px) => Some(px),
161 Self::Percent(pct) => Some(total.saturating_mul(pct.min(100) as u32) / 100),
162 Self::Ratio(num, den) => Some(total.saturating_mul(num) / den.max(1)),
163 Self::Fill(_) => None,
164 }
165 }
166
167 fn clamp(self, value: u32) -> u32 {
168 match self {
169 Self::Min(px) => value.max(px),
170 Self::Max(px) => value.min(px),
171 _ => value,
172 }
173 }
174
175 fn fill_weight(self) -> u32 {
176 match self {
177 Self::Fill(weight) => weight.max(1) as u32,
178 _ => 0,
179 }
180 }
181}
182
183pub type Length = Constraint;
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub struct LayoutItem {
187 pub main: Constraint,
188 pub cross: Constraint,
189 pub grow: u16,
190 pub shrink: u16,
191}
192
193impl LayoutItem {
194 pub const fn fixed(main: u32) -> Self {
195 Self::length(main)
196 }
197
198 pub const fn length(main: u32) -> Self {
199 Self {
200 main: Constraint::Length(main),
201 cross: Constraint::Fill(1),
202 grow: 0,
203 shrink: 1,
204 }
205 }
206
207 pub const fn fill() -> Self {
208 Self::fill_weight(1)
209 }
210
211 pub const fn fill_weight(weight: u16) -> Self {
212 Self {
213 main: Constraint::Fill(weight),
214 cross: Constraint::Fill(1),
215 grow: if weight == 0 { 1 } else { weight },
216 shrink: 1,
217 }
218 }
219
220 pub const fn percent(main: u8) -> Self {
221 Self {
222 main: Constraint::Percent(main),
223 cross: Constraint::Fill(1),
224 grow: 0,
225 shrink: 1,
226 }
227 }
228
229 pub const fn min(main: u32) -> Self {
230 Self {
231 main: Constraint::Min(main),
232 cross: Constraint::Fill(1),
233 grow: 0,
234 shrink: 1,
235 }
236 }
237
238 pub const fn max(main: u32) -> Self {
239 Self {
240 main: Constraint::Max(main),
241 cross: Constraint::Fill(1),
242 grow: 0,
243 shrink: 1,
244 }
245 }
246
247 pub const fn ratio(numerator: u32, denominator: u32) -> Self {
248 Self {
249 main: Constraint::Ratio(numerator, denominator),
250 cross: Constraint::Fill(1),
251 grow: 0,
252 shrink: 1,
253 }
254 }
255
256 pub const fn with_cross(mut self, cross: Constraint) -> Self {
257 self.cross = cross;
258 self
259 }
260
261 pub const fn with_grow(mut self, grow: u16) -> Self {
262 self.grow = grow;
263 self
264 }
265
266 pub const fn with_shrink(mut self, shrink: u16) -> Self {
267 self.shrink = shrink;
268 self
269 }
270
271 pub const fn flex(main: u32) -> Self {
272 Self::length(main).with_grow(1).with_shrink(1)
273 }
274
275 pub const fn rigid(main: u32) -> Self {
276 Self::length(main).with_grow(0).with_shrink(0)
277 }
278}
279
280impl LinearLayout {
281 pub fn arrange_items(&self, area: Rect, items: &[LayoutItem], out: &mut [Rect]) -> usize {
290 if items.is_empty() || out.is_empty() {
291 return 0;
292 }
293
294 let count = items.len().min(out.len());
295 let inner = area.inset(self.padding);
296 let main_total = match self.axis {
297 Axis::Horizontal => inner.w,
298 Axis::Vertical => inner.h,
299 };
300 let cross_total = match self.axis {
301 Axis::Horizontal => inner.h,
302 Axis::Vertical => inner.w,
303 };
304 let gap_total = self.gap as u32 * count.saturating_sub(1) as u32;
305 let available = main_total.saturating_sub(gap_total);
306 let mut fixed = 0u32;
307 let mut fill_weight = 0u32;
308
309 for item in items.iter().take(count) {
310 if let Some(px) = item.main.fixed_size(available) {
311 fixed = fixed.saturating_add(px);
312 } else {
313 fill_weight = fill_weight.saturating_add(item.main.fill_weight());
314 }
315 }
316
317 let remaining = available.saturating_sub(fixed);
318 let fill_unit = remaining.checked_div(fill_weight).unwrap_or(0);
319
320 let (mut cursor, item_gap) = if fill_weight == 0 && remaining > 0 {
321 let total_slack = remaining + gap_total;
322 match self.justify {
323 JustifyContent::Start => (
324 match self.axis {
325 Axis::Horizontal => inner.x,
326 Axis::Vertical => inner.y,
327 },
328 self.gap as i32,
329 ),
330 JustifyContent::Center => (
331 match self.axis {
332 Axis::Horizontal => inner.x + (remaining as i32 / 2),
333 Axis::Vertical => inner.y + (remaining as i32 / 2),
334 },
335 self.gap as i32,
336 ),
337 JustifyContent::End => (
338 match self.axis {
339 Axis::Horizontal => inner.x + remaining as i32,
340 Axis::Vertical => inner.y + remaining as i32,
341 },
342 self.gap as i32,
343 ),
344 JustifyContent::SpaceBetween => {
345 let step = if count > 1 {
346 total_slack / (count as u32 - 1)
347 } else {
348 0
349 };
350 (
351 match self.axis {
352 Axis::Horizontal => inner.x,
353 Axis::Vertical => inner.y,
354 },
355 step as i32,
356 )
357 }
358 JustifyContent::SpaceAround => {
359 let step = total_slack / count as u32;
360 let initial = step / 2;
361 (
362 match self.axis {
363 Axis::Horizontal => inner.x + initial as i32,
364 Axis::Vertical => inner.y + initial as i32,
365 },
366 step as i32,
367 )
368 }
369 JustifyContent::SpaceEvenly => {
370 let step = total_slack / (count as u32 + 1);
371 (
372 match self.axis {
373 Axis::Horizontal => inner.x + step as i32,
374 Axis::Vertical => inner.y + step as i32,
375 },
376 step as i32,
377 )
378 }
379 }
380 } else {
381 (
382 match self.axis {
383 Axis::Horizontal => inner.x,
384 Axis::Vertical => inner.y,
385 },
386 self.gap as i32,
387 )
388 };
389 let mut used_fill = 0u32;
390 let mut seen_fill_weight = 0u32;
391
392 for (slot, item) in out.iter_mut().zip(items.iter()).take(count) {
393 let main = if let Some(px) = item.main.fixed_size(available) {
394 px
395 } else {
396 let weight = item.main.fill_weight();
397 seen_fill_weight = seen_fill_weight.saturating_add(weight);
398 if seen_fill_weight >= fill_weight {
399 remaining.saturating_sub(used_fill)
400 } else {
401 let px = fill_unit.saturating_mul(weight);
402 used_fill = used_fill.saturating_add(px);
403 px
404 }
405 }
406 .min(available);
407 let main = item.main.clamp(main).min(available);
408 let cross = item
409 .cross
410 .fixed_size(cross_total)
411 .unwrap_or(cross_total)
412 .min(cross_total);
413 let cross = item.cross.clamp(cross).min(cross_total);
414 let cross_offset = match self.cross_align {
415 Align::Start | Align::Stretch => 0,
416 Align::Center => cross_total.saturating_sub(cross) as i32 / 2,
417 Align::End => cross_total.saturating_sub(cross) as i32,
418 };
419 let cross_size = if matches!(self.cross_align, Align::Stretch) {
420 cross_total
421 } else {
422 cross.min(cross_total)
423 };
424
425 *slot = match self.axis {
426 Axis::Horizontal => Rect::new(
427 cursor,
428 inner.y + cross_offset,
429 main.min(available),
430 cross_size,
431 ),
432 Axis::Vertical => Rect::new(
433 inner.x + cross_offset,
434 cursor,
435 cross_size,
436 main.min(available),
437 ),
438 };
439 cursor += main as i32 + item_gap;
440 }
441
442 count
443 }
444
445 pub fn arrange_items_flex(
446 &self,
447 area: Rect,
448 items: &[LayoutItem],
449 out: &mut [Rect],
450 enable_grow: bool,
451 enable_shrink: bool,
452 ) -> usize {
453 if items.is_empty() || out.is_empty() {
454 return 0;
455 }
456 let count = items.len().min(out.len());
457 let inner = area.inset(self.padding);
458 let main_total = match self.axis {
459 Axis::Horizontal => inner.w,
460 Axis::Vertical => inner.h,
461 };
462 let cross_total = match self.axis {
463 Axis::Horizontal => inner.h,
464 Axis::Vertical => inner.w,
465 };
466 let gap_total = self.gap as u32 * count.saturating_sub(1) as u32;
467 let available = main_total.saturating_sub(gap_total);
468
469 let mut grow_total = 0u32;
470 let mut shrink_total = 0u32;
471 let mut used = 0u32;
472 let mut fill_weight = 0u32;
473 for (idx, item) in items.iter().take(count).enumerate() {
474 if let Some(px) = item.main.fixed_size(available) {
475 let main = item.main.clamp(px).min(available);
476 out[idx].w = main;
477 used = used.saturating_add(main);
478 } else {
479 out[idx].w = 0;
480 fill_weight = fill_weight.saturating_add(item.main.fill_weight());
481 }
482 grow_total = grow_total.saturating_add(item.grow as u32);
483 shrink_total = shrink_total.saturating_add(item.shrink.max(1) as u32);
484 }
485 let remaining = available.saturating_sub(used);
486 let unit = remaining.checked_div(fill_weight).unwrap_or(0);
487 if fill_weight > 0 {
488 let mut seen = 0u32;
489 let mut used_fill = 0u32;
490 for (idx, item) in items.iter().take(count).enumerate() {
491 if item.main.fill_weight() == 0 {
492 continue;
493 }
494 let w = item.main.fill_weight();
495 seen = seen.saturating_add(w);
496 let px = if seen >= fill_weight {
497 remaining.saturating_sub(used_fill)
498 } else {
499 let part = unit.saturating_mul(w);
500 used_fill = used_fill.saturating_add(part);
501 part
502 };
503 let main = item.main.clamp(px).min(available);
504 out[idx].w = main;
505 used = used.saturating_add(main);
506 }
507 }
508
509 if enable_grow && used < available && grow_total > 0 {
510 let extra = available - used;
511 let unit = extra / grow_total;
512 let mut seen = 0u32;
513 let mut given = 0u32;
514 for (idx, item) in items.iter().take(count).enumerate() {
515 let w = item.grow as u32;
516 if w == 0 {
517 continue;
518 }
519 seen = seen.saturating_add(w);
520 let add = if seen >= grow_total {
521 extra.saturating_sub(given)
522 } else {
523 let part = unit.saturating_mul(w);
524 given = given.saturating_add(part);
525 part
526 };
527 out[idx].w = out[idx].w.saturating_add(add);
528 }
529 }
530
531 if enable_shrink && used > available && shrink_total > 0 {
532 let overflow = used - available;
533 let unit = overflow / shrink_total;
534 let mut seen = 0u32;
535 let mut taken = 0u32;
536 for (idx, item) in items.iter().take(count).enumerate() {
537 let w = item.shrink.max(1) as u32;
538 seen = seen.saturating_add(w);
539 let sub = if seen >= shrink_total {
540 overflow.saturating_sub(taken)
541 } else {
542 let part = unit.saturating_mul(w);
543 taken = taken.saturating_add(part);
544 part
545 };
546 out[idx].w = out[idx].w.saturating_sub(sub.min(out[idx].w));
547 }
548 }
549
550 let mut cursor = match self.axis {
551 Axis::Horizontal => inner.x,
552 Axis::Vertical => inner.y,
553 };
554 for idx in 0..count {
555 let item = items[idx];
556 let main = out[idx].w;
557 let cross = item
558 .cross
559 .fixed_size(cross_total)
560 .unwrap_or(cross_total)
561 .min(cross_total);
562 let cross = item.cross.clamp(cross).min(cross_total);
563 let cross_offset = match self.cross_align {
564 Align::Start | Align::Stretch => 0,
565 Align::Center => cross_total.saturating_sub(cross) as i32 / 2,
566 Align::End => cross_total.saturating_sub(cross) as i32,
567 };
568 let cross_size = if matches!(self.cross_align, Align::Stretch) {
569 cross_total
570 } else {
571 cross.min(cross_total)
572 };
573 out[idx] = match self.axis {
574 Axis::Horizontal => Rect::new(cursor, inner.y + cross_offset, main, cross_size),
575 Axis::Vertical => Rect::new(inner.x + cross_offset, cursor, cross_size, main),
576 };
577 cursor += main as i32 + self.gap as i32;
578 }
579 count
580 }
581}
582
583#[derive(Clone, Copy, Debug, PartialEq, Eq)]
585pub enum GridTrack {
586 Px(u32),
588 Fr(u8),
590 Auto,
592}
593
594impl GridTrack {
595 pub const fn px(pixels: u32) -> Self {
596 Self::Px(pixels)
597 }
598
599 pub const fn fr(weight: u8) -> Self {
600 Self::Fr(weight)
601 }
602
603 pub const fn auto() -> Self {
604 Self::Auto
605 }
606}
607
608#[derive(Clone, Copy, Debug, PartialEq, Eq)]
610pub struct GridPlacement {
611 pub col: usize,
612 pub row: usize,
613 pub col_span: usize,
614 pub row_span: usize,
615}
616
617impl GridPlacement {
618 pub const fn cell(col: usize, row: usize) -> Self {
619 Self {
620 col,
621 row,
622 col_span: 1,
623 row_span: 1,
624 }
625 }
626
627 pub const fn span(col: usize, row: usize, col_span: usize, row_span: usize) -> Self {
628 Self {
629 col,
630 row,
631 col_span: if col_span == 0 { 1 } else { col_span },
632 row_span: if row_span == 0 { 1 } else { row_span },
633 }
634 }
635}
636
637#[derive(Clone, Copy, Debug, PartialEq, Eq)]
639pub struct GridLayout<const COLS: usize, const ROWS: usize> {
640 pub col_tracks: [GridTrack; COLS],
641 pub row_tracks: [GridTrack; ROWS],
642 pub col_gap: u16,
643 pub row_gap: u16,
644 pub padding: EdgeInsets,
645}
646
647impl<const COLS: usize, const ROWS: usize> GridLayout<COLS, ROWS> {
648 pub const fn new(col_tracks: [GridTrack; COLS], row_tracks: [GridTrack; ROWS]) -> Self {
649 Self {
650 col_tracks,
651 row_tracks,
652 col_gap: 2,
653 row_gap: 2,
654 padding: EdgeInsets::all(0),
655 }
656 }
657
658 pub const fn uniform(col_gap: u16, row_gap: u16) -> Self {
659 Self {
660 col_tracks: [GridTrack::Auto; COLS],
661 row_tracks: [GridTrack::Auto; ROWS],
662 col_gap,
663 row_gap,
664 padding: EdgeInsets::all(0),
665 }
666 }
667
668 pub const fn with_gap(mut self, gap: u16) -> Self {
669 self.col_gap = gap;
670 self.row_gap = gap;
671 self
672 }
673
674 pub const fn with_col_gap(mut self, gap: u16) -> Self {
675 self.col_gap = gap;
676 self
677 }
678
679 pub const fn with_row_gap(mut self, gap: u16) -> Self {
680 self.row_gap = gap;
681 self
682 }
683
684 pub const fn with_padding(mut self, padding: EdgeInsets) -> Self {
685 self.padding = padding;
686 self
687 }
688
689 fn resolve_tracks(
691 available: u32,
692 start_pos: i32,
693 tracks: &[GridTrack],
694 gap: u16,
695 out_pos: &mut [i32],
696 out_sizes: &mut [u32],
697 ) {
698 let n = tracks.len();
699 if n == 0 {
700 return;
701 }
702
703 let total_gaps = ((n.saturating_sub(1)) as u32).saturating_mul(gap as u32);
704 let space = available.saturating_sub(total_gaps);
705
706 let mut fixed_sum: u32 = 0;
707 let mut total_fr: u32 = 0;
708 let mut auto_count: u32 = 0;
709
710 for track in tracks {
711 match *track {
712 GridTrack::Px(px) => fixed_sum = fixed_sum.saturating_add(px),
713 GridTrack::Fr(fr) => total_fr = total_fr.saturating_add(fr as u32),
714 GridTrack::Auto => auto_count = auto_count.saturating_add(1),
715 }
716 }
717
718 let remaining = space.saturating_sub(fixed_sum);
719
720 if auto_count > 0 && total_fr == 0 {
722 total_fr = auto_count;
723 } else if auto_count > 0 {
724 total_fr = total_fr.saturating_add(auto_count);
725 }
726
727 for (i, track) in tracks.iter().enumerate() {
729 let size = match *track {
730 GridTrack::Px(px) => px,
731 GridTrack::Fr(fr) => (remaining * fr as u32).checked_div(total_fr).unwrap_or(0),
732 GridTrack::Auto => remaining.checked_div(total_fr).unwrap_or(0),
733 };
734 out_sizes[i] = size;
735 }
736
737 let mut cur_pos = start_pos;
739 for i in 0..n {
740 out_pos[i] = cur_pos;
741 cur_pos += out_sizes[i] as i32 + gap as i32;
742 }
743 }
744
745 pub fn arrange_cells(
747 &self,
748 container: Rect,
749 placements: &[GridPlacement],
750 out: &mut [Rect],
751 ) -> usize {
752 let inner = container.inset(self.padding);
753 let mut col_pos = [0i32; COLS];
754 let mut col_sizes = [0u32; COLS];
755 let mut row_pos = [0i32; ROWS];
756 let mut row_sizes = [0u32; ROWS];
757
758 Self::resolve_tracks(
759 inner.w,
760 inner.x,
761 &self.col_tracks,
762 self.col_gap,
763 &mut col_pos,
764 &mut col_sizes,
765 );
766
767 Self::resolve_tracks(
768 inner.h,
769 inner.y,
770 &self.row_tracks,
771 self.row_gap,
772 &mut row_pos,
773 &mut row_sizes,
774 );
775
776 let count = placements.len().min(out.len());
777 for i in 0..count {
778 let p = placements[i];
779 let col = p.col.min(COLS.saturating_sub(1));
780 let row = p.row.min(ROWS.saturating_sub(1));
781 let col_end = (col + p.col_span).min(COLS);
782 let row_end = (row + p.row_span).min(ROWS);
783
784 let x = col_pos[col];
785 let y = row_pos[row];
786
787 let mut w: u32 = 0;
788 for (c, &size) in col_sizes.iter().enumerate().take(col_end).skip(col) {
789 w += size;
790 if c + 1 < col_end {
791 w += self.col_gap as u32;
792 }
793 }
794
795 let mut h: u32 = 0;
796 for (r, &size) in row_sizes.iter().enumerate().take(row_end).skip(row) {
797 h += size;
798 if r + 1 < row_end {
799 h += self.row_gap as u32;
800 }
801 }
802
803 out[i] = Rect::new(x, y, w, h);
804 }
805
806 count
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn test_linear_layout_column_presets() {
816 let col = LinearLayout::column();
817 assert_eq!(col.axis, Axis::Vertical);
818 assert_eq!(col.gap, 2);
819 assert_eq!(col.cross_align, Align::Stretch);
820
821 let row = LinearLayout::row();
822 assert_eq!(row.axis, Axis::Horizontal);
823 assert_eq!(row.gap, 2);
824 assert_eq!(row.cross_align, Align::Stretch);
825 }
826
827 #[test]
828 fn test_layout_arrange_row() {
829 let layout = LinearLayout {
830 axis: Axis::Horizontal,
831 gap: 5,
832 padding: EdgeInsets::all(10),
833 cross_align: Align::Stretch,
834 justify: JustifyContent::Start,
835 };
836
837 let container = Rect::new(0, 0, 100, 50);
838 let items = [
839 LayoutItem::fixed(20),
840 LayoutItem::fill(),
841 LayoutItem::fixed(30),
842 ];
843 let mut out = [Rect::empty(); 3];
844
845 let arranged = layout.arrange_items(container, &items, &mut out);
846 assert_eq!(arranged, 3);
847
848 assert_eq!(out[0].x, 10);
851 assert_eq!(out[0].w, 20);
852
853 assert_eq!(out[1].x, 35);
855 assert_eq!(out[1].w, 20);
856
857 assert_eq!(out[2].x, 60);
859 assert_eq!(out[2].w, 30);
860 }
861
862 #[test]
863 fn test_linear_layout_justify_content() {
864 let layout = LinearLayout::row()
865 .with_gap(0)
866 .with_justify(JustifyContent::SpaceBetween);
867
868 assert_eq!(layout.justify, JustifyContent::SpaceBetween);
869 }
870
871 #[test]
872 fn test_grid_layout_resolution_and_spans() {
873 let grid = GridLayout::<3, 2>::new(
874 [GridTrack::Px(50), GridTrack::Fr(1), GridTrack::Fr(2)],
875 [GridTrack::Px(30), GridTrack::Fr(1)],
876 )
877 .with_col_gap(10)
878 .with_row_gap(5)
879 .with_padding(EdgeInsets::all(10));
880
881 let container = Rect::new(0, 0, 320, 240);
882 let placements = [
883 GridPlacement::cell(0, 0), GridPlacement::span(1, 0, 2, 1), GridPlacement::span(0, 1, 3, 1), ];
887 let mut out = [Rect::empty(); 3];
888
889 let count = grid.arrange_cells(container, &placements, &mut out);
890 assert_eq!(count, 3);
891
892 assert_eq!(out[0].x, 10);
894 assert_eq!(out[0].y, 10);
895 assert_eq!(out[0].w, 50);
896 assert_eq!(out[0].h, 30);
897
898 assert_eq!(out[1].x, 70); assert_eq!(out[1].y, 10);
901 assert_eq!(out[1].h, 30);
902
903 assert_eq!(out[2].x, 10);
905 assert_eq!(out[2].y, 45); }
907}