embedded_layout/layout/linear/
spacing.rs

1//! Element spacing
2//!
3//! `ElementSpacing` can be used to change the distance of objects along the layout orientation.
4//! The default spacing is [`Tight`], which means objects are placed right next to each other,
5//! without any space between them.
6//!
7//! Change the default spacing by calling [`LinearLayout::with_spacing`]
8//!
9//! [`LinearLayout::with_spacing`]: crate::layout::linear::LinearLayout::with_spacing
10
11use crate::align::Alignment;
12use embedded_graphics::primitives::Rectangle;
13
14/// `ElementSpacing` base trait
15pub trait ElementSpacing: Copy + Clone {
16    /// Align `view` to `reference` using the element spacing rules
17    fn align(
18        &self,
19        alignment: impl Alignment,
20        view: Rectangle,
21        reference: Rectangle,
22        n: usize,
23        objects: usize,
24        total_size: u32,
25    ) -> i32;
26}
27
28/// Lay out objects tightly, leaving no space between them
29///
30/// # Example:
31/// ```rust
32/// use embedded_layout::{
33///     layout::linear::{spacing::Tight, LinearLayout},
34///     prelude::*,
35/// };
36/// use embedded_graphics::{prelude::*, primitives::Line};
37///
38/// let _ = LinearLayout::horizontal(
39///         Views::new(&mut [
40///             Line::new(Point::zero(), Point::new(0, 5)),
41///             Line::new(Point::zero(), Point::new(0, 5)),
42///             Line::new(Point::zero(), Point::new(0, 5)),
43///         ])
44///     )
45///     .with_spacing(Tight);
46/// ```
47#[derive(Copy, Clone)]
48pub struct Tight;
49impl ElementSpacing for Tight {
50    #[inline]
51    fn align(
52        &self,
53        alignment: impl Alignment,
54        view: Rectangle,
55        reference: Rectangle,
56        _n: usize,
57        _objects: usize,
58        _total_size: u32,
59    ) -> i32 {
60        alignment.align_with_offset(view, reference, 0)
61    }
62}
63
64/// Lay out objects with fixed margin between them
65///
66/// The margin can be negative, in which case the elements will be placed over one another.
67///
68/// # Example:
69/// ```
70/// use embedded_layout::{
71///     layout::linear::{spacing::FixedMargin, LinearLayout},
72///     prelude::*,
73/// };
74/// use embedded_graphics::{prelude::*, primitives::Line};
75///
76/// // Apply a 3px margin between objects
77/// let _ = LinearLayout::horizontal(
78///         Views::new(&mut [
79///             Line::new(Point::zero(), Point::new(0, 5)),
80///             Line::new(Point::zero(), Point::new(0, 5)),
81///             Line::new(Point::zero(), Point::new(0, 5)),
82///         ])
83///     )
84///     .with_spacing(FixedMargin(3));
85/// ```
86#[derive(Copy, Clone)]
87pub struct FixedMargin(pub i32);
88impl ElementSpacing for FixedMargin {
89    #[inline]
90    fn align(
91        &self,
92        alignment: impl Alignment,
93        view: Rectangle,
94        reference: Rectangle,
95        n: usize,
96        _objects: usize,
97        _total_size: u32,
98    ) -> i32 {
99        let offset = if n == 0 { 0 } else { self.0 };
100        alignment.align_with_offset(view, reference, offset)
101    }
102}
103
104/// Distribute views to fill a given space
105///
106/// Forces the layout to be as high or wide as set for this spacing
107///
108/// # Example:
109/// ```rust
110/// use embedded_layout::{
111///     layout::linear::{spacing::DistributeFill, LinearLayout},
112///     prelude::*,
113/// };
114/// use embedded_graphics::{prelude::*, primitives::Line};
115///
116/// // Distribute views in a 64px high space
117/// let _ = LinearLayout::vertical(
118///         Views::new(&mut [
119///             Line::new(Point::zero(), Point::new(0, 5)),
120///             Line::new(Point::zero(), Point::new(0, 5)),
121///             Line::new(Point::zero(), Point::new(0, 5)),
122///         ])
123///     )
124///     .with_spacing(DistributeFill(64));
125/// ```
126#[derive(Copy, Clone)]
127pub struct DistributeFill(pub u32);
128impl ElementSpacing for DistributeFill {
129    #[inline]
130    fn align(
131        &self,
132        alignment: impl Alignment,
133        view: Rectangle,
134        reference: Rectangle,
135        n: usize,
136        objects: usize,
137        total_size: u32,
138    ) -> i32 {
139        // bit of a mess, but calculate using i32 in case the views don't fit the space
140        let empty_space = self.0 as i32 - total_size as i32;
141
142        if objects <= 1 {
143            return alignment.align_with_offset(view, reference, empty_space / 2);
144        }
145
146        let base = empty_space / (objects - 1) as i32;
147        let remainder = empty_space % (objects - 1) as i32;
148
149        let offset = if n == 0 {
150            0
151        } else if n as i32 <= remainder {
152            base + 1
153        } else {
154            base
155        };
156        alignment.align_with_offset(view, reference, offset)
157    }
158}