1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Element spacing
//!
//! `ElementSpacing` can be used to change the distance of objects along the layout orientation.
//! The default spacing is [`Tight`], which means objects are placed right next to each other,
//! without any space between them.
//!
//! Change the default spacing by calling [`LinearLayout::with_spacing`]
//!
//! [`LinearLayout::with_spacing`]: crate::layout::linear::LinearLayout::with_spacing

use crate::align::Alignment;
use embedded_graphics::primitives::Rectangle;

/// `ElementSpacing` base trait
pub trait ElementSpacing: Copy + Clone {
    /// Align `view` to `reference` using the element spacing rules
    fn align(
        &self,
        alignment: impl Alignment,
        view: Rectangle,
        reference: Rectangle,
        n: usize,
        objects: usize,
        total_size: u32,
    ) -> i32;
}

/// Lay out objects tightly, leaving no space between them
///
/// # Example:
/// ```rust
/// use embedded_layout::{
///     layout::linear::{spacing::Tight, LinearLayout},
///     prelude::*,
/// };
/// use embedded_graphics::{prelude::*, primitives::Line};
///
/// let _ = LinearLayout::horizontal(
///         Views::new(&mut [
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///         ])
///     )
///     .with_spacing(Tight);
/// ```
#[derive(Copy, Clone)]
pub struct Tight;
impl ElementSpacing for Tight {
    #[inline]
    fn align(
        &self,
        alignment: impl Alignment,
        view: Rectangle,
        reference: Rectangle,
        _n: usize,
        _objects: usize,
        _total_size: u32,
    ) -> i32 {
        alignment.align_with_offset(view, reference, 0)
    }
}

/// Lay out objects with fixed margin between them
///
/// The margin can be negative, in which case the elements will be placed over one another.
///
/// # Example:
/// ```
/// use embedded_layout::{
///     layout::linear::{spacing::FixedMargin, LinearLayout},
///     prelude::*,
/// };
/// use embedded_graphics::{prelude::*, primitives::Line};
///
/// // Apply a 3px margin between objects
/// let _ = LinearLayout::horizontal(
///         Views::new(&mut [
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///         ])
///     )
///     .with_spacing(FixedMargin(3));
/// ```
#[derive(Copy, Clone)]
pub struct FixedMargin(pub i32);
impl ElementSpacing for FixedMargin {
    #[inline]
    fn align(
        &self,
        alignment: impl Alignment,
        view: Rectangle,
        reference: Rectangle,
        n: usize,
        _objects: usize,
        _total_size: u32,
    ) -> i32 {
        let offset = if n == 0 { 0 } else { self.0 };
        alignment.align_with_offset(view, reference, offset)
    }
}

/// Distribute views to fill a given space
///
/// Forces the layout to be as high or wide as set for this spacing
///
/// # Example:
/// ```rust
/// use embedded_layout::{
///     layout::linear::{spacing::DistributeFill, LinearLayout},
///     prelude::*,
/// };
/// use embedded_graphics::{prelude::*, primitives::Line};
///
/// // Distribute views in a 64px high space
/// let _ = LinearLayout::vertical(
///         Views::new(&mut [
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///             Line::new(Point::zero(), Point::new(0, 5)),
///         ])
///     )
///     .with_spacing(DistributeFill(64));
/// ```
#[derive(Copy, Clone)]
pub struct DistributeFill(pub u32);
impl ElementSpacing for DistributeFill {
    #[inline]
    fn align(
        &self,
        alignment: impl Alignment,
        view: Rectangle,
        reference: Rectangle,
        n: usize,
        objects: usize,
        total_size: u32,
    ) -> i32 {
        // bit of a mess, but calculate using i32 in case the views don't fit the space
        let empty_space = self.0 as i32 - total_size as i32;
        let base = empty_space / (objects - 1) as i32;
        let remainder = empty_space % (objects - 1) as i32;

        let offset = if n == 0 {
            0
        } else if n as i32 <= remainder {
            base + 1
        } else {
            base
        };
        alignment.align_with_offset(view, reference, offset)
    }
}