embedded_layout/layout/linear/
mod.rs

1//! Linear layout
2//!
3//! A linear layout is a list of [`View`]s that are placed one after the other along
4//! the horizontal or vertical axis.
5//!
6//! The main flow when working with a [`LinearLayout`] is the following:
7//!  - Create the layout
8//!    * you need to choose which orientation you want your views arranged in
9//!    * pass in your views wrapped in a [`ViewGroup`].
10//!  - Optionally, set [secondary alignment]
11//!  - Optionally, set [element spacing]
12//!  - Call [`LinearLayout::arrange`] to finalize view placement
13//!  - Align the layout object to where you want it to be displayed
14//!  - Call `draw` to display the views
15//!
16//! # Orientation
17//!
18//! When constructing a [`LinearLayout`] object, you need to choose an orientation along which
19//! the views will be arranged. This can either be horizontal or vertical.
20//!
21//! ## Examples:
22//!
23//! Create a [`LinearLayout`] with two pieces of text, where one is below the other:
24//!
25//! ```rust
26//! # use embedded_layout::prelude::*;
27//! # use embedded_layout::layout::linear::LinearLayout;
28//! # use embedded_graphics::{
29//! #     mono_font::{ascii::FONT_6X9, MonoTextStyle},
30//! #     pixelcolor::BinaryColor,
31//! #     text::Text,
32//! #     prelude::*,
33//! # };
34//! let text_style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
35//! let _ = LinearLayout::vertical(
36//!     Chain::new(Text::new("Hello,", Point::zero(), text_style))
37//!         .append(Text::new("World!", Point::zero(), text_style)),
38//! )
39//! .arrange();
40//! ```
41//!
42//! # Secondary alignment
43//!
44//! Secondary alignment means the alignment on the "other" axis:
45//!  - horizontal alignment in vertical linear layouts
46//!  - vertical alignment in horizontal linear layouts
47//!
48//! By default, the secondary alignments are the following:
49//!  - Horizontal orientation: [`vertical::Bottom`]
50//!  - Vertical orientation: [`horizontal::Left`]
51//!
52//! Except for using the cascading (`XtoY`) secondary alignments, the [`LinearLayout`] will take up
53//! as much space along the secondary alignment as the biggest element, i.e. vertical layouts
54//! will be as wide as the widest view inside them.
55//!
56//! # Element spacing
57//!
58//! It's possible to modify how views are placed relative to one another.
59//!  * The default is [`Tight`] which is equivalent to [`FixedMargin(0)`]
60//!  * [`FixedMargin(margin)`]: `margin` px distance between views, where `margin` can be negative to overlap views
61//!  * [`DistributeFill(size)`]: force the primary layout size to `size`, distribute views evenly
62//!
63//! [`View`]: crate::View
64//! [`ViewGroup`]: crate::view_group::ViewGroup
65//! [secondary alignment]: LinearLayout::with_alignment
66//! [element spacing]: LinearLayout::with_spacing
67//! [`Tight`]: crate::layout::linear::spacing::Tight
68//! [`FixedMargin(0)`]: crate::layout::linear::spacing::FixedMargin
69//! [`FixedMargin(margin)`]: crate::layout::linear::spacing::FixedMargin
70//! [`DistributeFill(size)`]: crate::layout::linear::spacing::DistributeFill
71//! [`vertical::Bottom`]: crate::align::vertical::Bottom
72//! [`horizontal::Left`]: crate::align::horizontal::Left
73
74use 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
97/// `LinearLayout`
98///
99/// [`LinearLayout`] is used to arrange views along the horizontal or vertical axis.
100///
101/// For more information and examples see the [module level documentation](crate::layout::linear).
102pub struct LinearLayout<LD, VG> {
103    position: Point,
104    direction: LD,
105    views: VG,
106}
107
108impl<LD, VG> LinearLayout<LD, VG> {
109    /// Returns a reference to the contained views.
110    #[inline]
111    pub fn inner(&self) -> &VG {
112        &self.views
113    }
114
115    /// Returns a mutable reference to the contained views.
116    #[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    /// Create a new [`LinearLayout`] that places views left to right
127    #[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    /// Create a new [`LinearLayout`] that places views top to bottom
143    #[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    /// Change the secondary alignment for this [`LinearLayout`] object.
161    ///
162    /// For layouts created using [`LinearLayout::horizontal`] the secondary alignment is [`vertical`].
163    ///
164    /// [`LinearLayout::horizontal`]: crate::layout::linear::LinearLayout::horizontal
165    /// [`vertical`]: crate::align::vertical
166    #[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    /// Change the element spacing
179    ///
180    /// For available values and their properties, see [spacing]
181    ///
182    /// [spacing]: crate::layout::linear::spacing
183    #[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    /// Change the secondary alignment for this [`LinearLayout`] object.
203    ///
204    /// For layouts created using [`LinearLayout::vertical`] the secondary alignment is [`horizontal`].
205    ///
206    /// [`LinearLayout::vertical`]: crate::layout::linear::LinearLayout::vertical
207    /// [`horizontal`]: crate::align::horizontal
208    #[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    /// Change the element spacing
221    ///
222    /// For available values and their properties, see [spacing]
223    ///
224    /// [spacing]: crate::layout::linear::spacing
225    #[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    /// Consume the layout object and return the wrapped [`ViewGroup`].
258    ///
259    /// After calling `arrange()` it is no longer necessary to hold the views in a `LinearLayout`.
260    /// Use this method to extract the original view group object if you need to work with the
261    /// arranged views.
262    ///
263    /// # Example
264    ///
265    /// Arrange an array of `StyledText` objects, then check the second object's position.
266    ///
267    /// ```rust
268    /// # use embedded_layout::prelude::*;
269    /// # use embedded_layout::layout::linear::LinearLayout;
270    /// # use embedded_graphics::{
271    /// #     mono_font::{ascii::FONT_6X9, MonoTextStyle},
272    /// #     pixelcolor::BinaryColor,
273    /// #     prelude::*,
274    /// #     mock_display::MockDisplay,
275    /// #     text::Text,
276    /// # };
277    /// # let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
278    /// #
279    /// let text_style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
280    ///
281    /// // First, wrap out views in a `ViewGroup`.
282    /// let mut texts = [
283    ///     Text::new("Hello,", Point::zero(), text_style),
284    ///     Text::new("World!", Point::zero(), text_style)
285    /// ];
286    /// let mut views = Views::new(&mut texts);
287    ///
288    /// // Arrange our views and extract our original view group.
289    /// let views = LinearLayout::vertical(views).arrange().into_inner();
290    ///
291    /// // We can access our `StyledText` objects now. Note that `Views` works like a slice!
292    /// assert_eq!(Point::new(0, 9), views[1].bounds().top_left);
293    ///
294    /// // `Views` is also a drawable `ViewGroup`, so let's display our arranged text!
295    /// views.draw(&mut display).unwrap();
296    /// ```
297    #[inline]
298    #[must_use]
299    pub fn into_inner(self) -> VG {
300        self.views
301    }
302
303    /// Arrange the views according to the layout properties and return the views as a [`ViewGroup`].
304    #[inline]
305    #[must_use]
306    pub fn arrange(mut self) -> Self {
307        // Place first child to the layout's position.
308        self.views
309            .translate_child(0, self.position - self.views.bounds_of(0).top_left);
310
311        // We can't use `self` because we borrow parts of it mutably.
312        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    /// Arrange a [`ViewGroup`] according to the layout properties.
323    #[inline]
324    pub fn arrange_view_group(&self, view_group: &mut impl ViewGroup) {
325        let view_count = view_group.len();
326
327        // measure
328        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        // arrange
337        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}