1use crate::{
75 align::{horizontal, vertical},
76 align::{HorizontalAlignment, VerticalAlignment},
77 view_group::{EmptyViewGroup, ViewGroup},
78 View,
79};
80
81mod orientation;
82mod secondary_alignment;
83pub mod spacing;
84
85use embedded_graphics::{
86 draw_target::DrawTarget,
87 prelude::{PixelColor, Point},
88 primitives::Rectangle,
89 Drawable,
90};
91pub use orientation::{Horizontal, Orientation, Vertical};
92pub use secondary_alignment::SecondaryAlignment;
93pub use spacing::{ElementSpacing, FixedMargin};
94
95use spacing::Tight;
96
97pub struct LinearLayout<LD, VG> {
103 position: Point,
104 direction: LD,
105 views: VG,
106}
107
108impl<LD, VG> LinearLayout<LD, VG> {
109 #[inline]
111 pub fn inner(&self) -> &VG {
112 &self.views
113 }
114
115 #[inline]
117 pub fn inner_mut(&mut self) -> &mut VG {
118 &mut self.views
119 }
120}
121
122impl<VG> LinearLayout<Horizontal<vertical::Bottom, Tight>, VG>
123where
124 VG: ViewGroup,
125{
126 #[inline]
128 #[must_use]
129 pub fn horizontal(views: VG) -> Self {
130 Self {
131 position: Point::new(0, 0),
132 direction: Horizontal::default(),
133 views,
134 }
135 }
136}
137
138impl<VG> LinearLayout<Vertical<horizontal::Left, Tight>, VG>
139where
140 VG: ViewGroup,
141{
142 #[inline]
144 #[must_use]
145 pub fn vertical(views: VG) -> Self {
146 Self {
147 position: Point::new(0, 0),
148 direction: Vertical::default(),
149 views,
150 }
151 }
152}
153
154impl<S, ELS, VG> LinearLayout<Horizontal<S, ELS>, VG>
155where
156 S: SecondaryAlignment + VerticalAlignment,
157 ELS: ElementSpacing,
158 VG: ViewGroup,
159{
160 #[inline]
167 pub fn with_alignment<Sec>(self, alignment: Sec) -> LinearLayout<Horizontal<Sec, ELS>, VG>
168 where
169 Sec: SecondaryAlignment + VerticalAlignment,
170 {
171 LinearLayout {
172 position: self.position,
173 direction: self.direction.with_secondary_alignment(alignment),
174 views: self.views,
175 }
176 }
177
178 #[inline]
184 pub fn with_spacing<ES>(self, spacing: ES) -> LinearLayout<Horizontal<S, ES>, VG>
185 where
186 ES: ElementSpacing,
187 {
188 LinearLayout {
189 position: self.position,
190 direction: self.direction.with_spacing(spacing),
191 views: self.views,
192 }
193 }
194}
195
196impl<S, ELS, VG> LinearLayout<Vertical<S, ELS>, VG>
197where
198 S: SecondaryAlignment + HorizontalAlignment,
199 ELS: ElementSpacing,
200 VG: ViewGroup,
201{
202 #[inline]
209 pub fn with_alignment<Sec>(self, alignment: Sec) -> LinearLayout<Vertical<Sec, ELS>, VG>
210 where
211 Sec: SecondaryAlignment + HorizontalAlignment,
212 {
213 LinearLayout {
214 position: self.position,
215 direction: self.direction.with_secondary_alignment(alignment),
216 views: self.views,
217 }
218 }
219
220 #[inline]
226 pub fn with_spacing<ES>(self, spacing: ES) -> LinearLayout<Vertical<S, ES>, VG>
227 where
228 ES: ElementSpacing,
229 {
230 LinearLayout {
231 position: self.position,
232 direction: self.direction.with_spacing(spacing),
233 views: self.views,
234 }
235 }
236}
237
238impl<LD, VG> Clone for LinearLayout<LD, VG>
239where
240 LD: Orientation,
241 VG: ViewGroup + Clone,
242{
243 fn clone(&self) -> Self {
244 Self {
245 position: self.position,
246 direction: self.direction,
247 views: self.views.clone(),
248 }
249 }
250}
251
252impl<LD, VG> LinearLayout<LD, VG>
253where
254 LD: Orientation,
255 VG: ViewGroup,
256{
257 #[inline]
298 #[must_use]
299 pub fn into_inner(self) -> VG {
300 self.views
301 }
302
303 #[inline]
305 #[must_use]
306 pub fn arrange(mut self) -> Self {
307 self.views
309 .translate_child(0, self.position - self.views.bounds_of(0).top_left);
310
311 LinearLayout {
313 position: Point::zero(),
314 direction: self.direction,
315 views: EmptyViewGroup,
316 }
317 .arrange_view_group(&mut self.views);
318
319 self
320 }
321
322 #[inline]
324 pub fn arrange_view_group(&self, view_group: &mut impl ViewGroup) {
325 let view_count = view_group.len();
326
327 let bounds = view_group.bounds_of(0);
329 let position = bounds.top_left;
330 let mut size = bounds.size();
331 for i in 1..view_count {
332 let current_el_size = view_group.bounds_of(i).size();
333 size = LD::Secondary::measure(size, current_el_size);
334 }
335
336 let mut bounds = Rectangle::new(position, size);
338 for i in 0..view_count {
339 let offset =
340 self.direction
341 .compute_offset(view_group.bounds_of(i), size, bounds, i, view_count);
342 view_group.translate_child(i, offset);
343 bounds = view_group.bounds_of(i);
344 }
345 }
346}
347
348impl<LD, VG> View for LinearLayout<LD, VG>
349where
350 LD: Orientation,
351 VG: ViewGroup,
352{
353 #[inline]
354 fn translate_impl(&mut self, by: Point) {
355 self.position += by;
356 View::translate_impl(&mut self.views, by);
357 }
358
359 #[inline]
360 fn bounds(&self) -> Rectangle {
361 let bounds = View::bounds(&self.views);
362 let top_left = bounds.top_left;
363 let correction = self.position - top_left;
364
365 bounds.translate(correction)
366 }
367}
368
369impl<LD, VG> ViewGroup for LinearLayout<LD, VG>
370where
371 LD: Orientation,
372 VG: ViewGroup,
373{
374 #[inline]
375 fn len(&self) -> usize {
376 self.views.len()
377 }
378
379 #[inline]
380 fn at(&self, idx: usize) -> &dyn View {
381 self.views.at(idx)
382 }
383
384 #[inline]
385 fn at_mut(&mut self, idx: usize) -> &mut dyn View {
386 self.views.at_mut(idx)
387 }
388
389 #[inline]
390 fn bounds_of(&self, idx: usize) -> Rectangle {
391 self.views.bounds_of(idx)
392 }
393
394 #[inline]
395 fn translate_child(&mut self, idx: usize, by: Point) {
396 self.views.translate_child(idx, by)
397 }
398}
399
400impl<C, LD, VG> Drawable for LinearLayout<LD, VG>
401where
402 C: PixelColor,
403 LD: Orientation,
404 VG: ViewGroup + Drawable<Color = C>,
405{
406 type Color = C;
407 type Output = ();
408
409 #[inline]
410 fn draw<D>(&self, display: &mut D) -> Result<(), D::Error>
411 where
412 D: DrawTarget<Color = C>,
413 {
414 self.views.draw(display)?;
415 Ok(())
416 }
417}
418
419#[cfg(test)]
420mod test {
421 use crate::{
422 layout::linear::{
423 spacing::{DistributeFill, FixedMargin},
424 LinearLayout,
425 },
426 object_chain::Chain,
427 prelude::*,
428 };
429 use embedded_graphics::{
430 mock_display::MockDisplay,
431 pixelcolor::BinaryColor,
432 prelude::{Point, Primitive, Size},
433 primitives::{Circle, PrimitiveStyle, Rectangle},
434 Drawable,
435 };
436
437 #[allow(dead_code)]
438 fn compile_check() {
439 let style = PrimitiveStyle::with_fill(BinaryColor::On);
440 let rect = Rectangle::new(Point::zero(), Size::new(10, 20)).into_styled(style);
441 let circ = Circle::new(Point::zero(), 10).into_styled(style);
442 let _ = LinearLayout::horizontal(Chain::new(rect).append(circ));
443 }
444
445 #[test]
446 fn layout_size() {
447 let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
448 let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
449 let size = LinearLayout::horizontal(Chain::new(rect).append(rect2))
450 .arrange()
451 .size();
452
453 assert_eq!(Size::new(20, 20), size);
454
455 let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
456 let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
457 let size = LinearLayout::vertical(Chain::new(rect).append(rect2))
458 .arrange()
459 .size();
460
461 assert_eq!(Size::new(10, 40), size);
462 }
463
464 #[test]
465 fn layout_arrange_vertical() {
466 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
467
468 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
469 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
470 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
471
472 LinearLayout::vertical(Chain::new(rect).append(rect2))
473 .arrange()
474 .translate(Point::new(1, 2))
475 .draw(&mut disp)
476 .unwrap();
477
478 assert_eq!(
479 disp,
480 MockDisplay::from_pattern(&[
481 " ",
482 " ",
483 " ##########",
484 " # #",
485 " # #",
486 " # #",
487 " ##########",
488 " ##### ",
489 " # # ",
490 " # # ",
491 " # # ",
492 " # # ",
493 " # # ",
494 " # # ",
495 " # # ",
496 " # # ",
497 " ##### ",
498 ])
499 );
500 }
501
502 #[test]
503 fn empty_rectangle_takes_up_no_vertical_space() {
504 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
505
506 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
507 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
508 let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style);
509 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
510
511 LinearLayout::vertical(Chain::new(rect).append(rect_empty).append(rect2))
512 .arrange()
513 .translate(Point::new(1, 2))
514 .draw(&mut disp)
515 .unwrap();
516
517 assert_eq!(
518 disp,
519 MockDisplay::from_pattern(&[
520 " ",
521 " ",
522 " ##########",
523 " # #",
524 " # #",
525 " # #",
526 " ##########",
527 " ##### ",
528 " # # ",
529 " # # ",
530 " # # ",
531 " # # ",
532 " # # ",
533 " # # ",
534 " # # ",
535 " # # ",
536 " ##### ",
537 ])
538 );
539 }
540
541 #[test]
542 fn layout_arrange_vertical_secondary() {
543 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
544
545 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
546 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
547 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
548
549 LinearLayout::vertical(Chain::new(rect).append(rect2))
550 .with_alignment(horizontal::Right)
551 .arrange()
552 .translate(Point::new(1, 2))
553 .draw(&mut disp)
554 .unwrap();
555
556 assert_eq!(
557 disp,
558 MockDisplay::from_pattern(&[
559 " ",
560 " ",
561 " ##########",
562 " # #",
563 " # #",
564 " # #",
565 " ##########",
566 " #####",
567 " # #",
568 " # #",
569 " # #",
570 " # #",
571 " # #",
572 " # #",
573 " # #",
574 " # #",
575 " #####",
576 ])
577 );
578 }
579
580 #[test]
581 fn layout_arrange_horizontal() {
582 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
583
584 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
585 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
586 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
587
588 LinearLayout::horizontal(Chain::new(rect).append(rect2))
589 .arrange()
590 .translate(Point::new(1, 2))
591 .draw(&mut disp)
592 .unwrap();
593
594 assert_eq!(
595 disp,
596 MockDisplay::from_pattern(&[
597 " ",
598 " ",
599 " #####",
600 " # #",
601 " # #",
602 " # #",
603 " # #",
604 " ########### #",
605 " # ## #",
606 " # ## #",
607 " # ## #",
608 " ###############",
609 ])
610 );
611 }
612
613 #[test]
614 fn empty_rectangle_takes_up_no_horizontal_space() {
615 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
616
617 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
618 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
619 let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style);
620 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
621
622 LinearLayout::horizontal(Chain::new(rect).append(rect_empty).append(rect2))
623 .arrange()
624 .translate(Point::new(1, 2))
625 .draw(&mut disp)
626 .unwrap();
627
628 assert_eq!(
629 disp,
630 MockDisplay::from_pattern(&[
631 " ",
632 " ",
633 " #####",
634 " # #",
635 " # #",
636 " # #",
637 " # #",
638 " ########### #",
639 " # ## #",
640 " # ## #",
641 " # ## #",
642 " ###############",
643 ])
644 );
645 }
646
647 #[test]
648 fn layout_arrange_horizontal_secondary() {
649 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
650
651 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
652 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
653 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
654
655 LinearLayout::horizontal(Chain::new(rect).append(rect2))
656 .with_alignment(vertical::Top)
657 .arrange()
658 .translate(Point::new(1, 2))
659 .draw(&mut disp)
660 .unwrap();
661
662 assert_eq!(
663 disp,
664 MockDisplay::from_pattern(&[
665 " ",
666 " ",
667 " ###############",
668 " # ## #",
669 " # ## #",
670 " # ## #",
671 " ########### #",
672 " # #",
673 " # #",
674 " # #",
675 " # #",
676 " #####",
677 ])
678 );
679 }
680
681 #[test]
682 fn layout_spacing_size() {
683 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
684
685 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
686 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
687
688 let size = LinearLayout::horizontal(Chain::new(rect).append(rect2))
689 .with_spacing(FixedMargin(2))
690 .with_alignment(vertical::Top)
691 .arrange()
692 .size();
693
694 assert_eq!(Size::new(17, 10), size);
695
696 let size = LinearLayout::vertical(Chain::new(rect).append(rect2))
697 .with_spacing(FixedMargin(2))
698 .arrange()
699 .size();
700
701 assert_eq!(Size::new(10, 17), size);
702 }
703
704 #[test]
705 fn layout_spacing() {
706 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
707
708 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
709
710 let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style);
711 let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style);
712
713 LinearLayout::horizontal(Chain::new(rect).append(rect2))
714 .with_spacing(FixedMargin(2))
715 .with_alignment(vertical::Top)
716 .arrange()
717 .translate(Point::new(1, 2))
718 .draw(&mut disp)
719 .unwrap();
720
721 assert_eq!(
722 disp,
723 MockDisplay::from_pattern(&[
724 " ",
725 " ",
726 " ########## #####",
727 " # # # #",
728 " # # # #",
729 " # # # #",
730 " ########## # #",
731 " # #",
732 " # #",
733 " # #",
734 " # #",
735 " #####",
736 ])
737 );
738 }
739
740 #[test]
741 fn layout_spacing_distribute_overflow() {
742 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
743
744 let rect = Rectangle::new(Point::zero(), Size::new(5, 5)).into_styled(style);
745
746 let layout = LinearLayout::horizontal(Chain::new(rect).append(rect).append(rect))
747 .with_spacing(DistributeFill(11))
748 .with_alignment(vertical::TopToBottom)
749 .arrange();
750
751 assert_eq!(Size::new(11, 15), layout.size());
752
753 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
754
755 layout.arrange().draw(&mut disp).unwrap();
756 assert_eq!(
757 disp,
758 MockDisplay::from_pattern(&[
759 "##### ",
760 "# # ",
761 "# # ",
762 "# # ",
763 "##### ",
764 " ##### ",
765 " # # ",
766 " # # ",
767 " # # ",
768 " ##### ",
769 " #####",
770 " # #",
771 " # #",
772 " # #",
773 " #####",
774 ])
775 );
776 }
777
778 #[test]
779 fn layout_spacing_distribute_fill() {
780 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1);
781
782 let rect = Rectangle::new(Point::zero(), Size::new(2, 2)).into_styled(style);
783
784 let view_group =
785 LinearLayout::vertical(Chain::new(rect).append(rect).append(rect).append(rect))
786 .with_spacing(DistributeFill(18))
787 .arrange();
788
789 let mut disp: MockDisplay<BinaryColor> = MockDisplay::new();
790
791 view_group.draw(&mut disp).unwrap();
792 assert_eq!(
793 disp,
794 MockDisplay::from_pattern(&[
795 "## ",
796 "## ",
797 " ",
798 " ",
799 " ",
800 " ",
801 "## ",
802 "## ",
803 " ",
804 " ",
805 " ",
806 "## ",
807 "## ",
808 " ",
809 " ",
810 " ",
811 "## ",
812 "## "
813 ])
814 );
815 }
816
817 #[test]
818 fn layout_size_independent_of_view_location() {
819 let rect = Rectangle::new(Point::zero(), Size::new(10, 20));
820 let rect2 = Rectangle::new(Point::zero(), Size::new(10, 20));
821
822 let size1 = LinearLayout::horizontal(Chain::new(rect).append(rect2))
823 .arrange()
824 .bounds()
825 .size();
826
827 let rect = rect.translate(Point::new(30, 50));
828
829 let size2 = LinearLayout::horizontal(Chain::new(rect).append(rect2))
830 .arrange()
831 .bounds()
832 .size();
833
834 assert_eq!(size1, size2);
835 }
836}