Skip to main content

sequoia_layout/
container.rs

1use crate::dock::sanitize_gap;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub enum Axis {
5    Horizontal,
6    Vertical,
7}
8
9impl Axis {
10    pub const fn is_horizontal(self) -> bool {
11        matches!(self, Self::Horizontal)
12    }
13
14    pub const fn is_vertical(self) -> bool {
15        matches!(self, Self::Vertical)
16    }
17
18    pub fn split_main_cross(self, width: f32, height: f32) -> (f32, f32) {
19        match self {
20            Self::Horizontal => (width, height),
21            Self::Vertical => (height, width),
22        }
23    }
24
25    pub fn merge_main_cross(self, main: f32, cross: f32) -> (f32, f32) {
26        match self {
27            Self::Horizontal => (main, cross),
28            Self::Vertical => (cross, main),
29        }
30    }
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum Justify {
36    #[default]
37    Start,
38    Center,
39    End,
40    SpaceBetween,
41    SpaceAround,
42    SpaceEvenly,
43}
44
45impl Justify {
46    pub fn distribute(self, count: usize, _base_gap: f32, extra: f32) -> (f32, f32) {
47        let extra = if extra.is_finite() {
48            extra.max(0.0)
49        } else {
50            0.0
51        };
52        match self {
53            Self::Start => (0.0, 0.0),
54            Self::Center => (extra * 0.5, 0.0),
55            Self::End => (extra, 0.0),
56            Self::SpaceBetween => {
57                if count > 1 {
58                    (0.0, extra / (count - 1) as f32)
59                } else if count == 1 {
60                    (extra * 0.5, 0.0)
61                } else {
62                    (0.0, 0.0)
63                }
64            }
65            Self::SpaceAround => {
66                if count > 0 {
67                    let step = extra / count as f32;
68                    (step * 0.5, step)
69                } else {
70                    (0.0, 0.0)
71                }
72            }
73            Self::SpaceEvenly => {
74                if count > 0 {
75                    let step = extra / (count + 1) as f32;
76                    (step, step)
77                } else {
78                    (0.0, 0.0)
79                }
80            }
81        }
82    }
83
84    pub fn start_offset(self, extra: f32) -> f32 {
85        let (start, _) = self.distribute(0, 0.0, extra);
86        match self {
87            Self::Start | Self::Center | Self::End => start,
88            _ => start,
89        }
90    }
91}
92
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
94pub enum Align {
95    #[default]
96    Start,
97    Center,
98    End,
99    Stretch,
100}
101
102impl Align {
103    pub const fn is_stretch(self) -> bool {
104        matches!(self, Self::Stretch)
105    }
106
107    pub fn offset(self, avail: f32, child: f32) -> f32 {
108        if !avail.is_finite() {
109            return 0.0;
110        }
111        let slack = (avail - child).max(0.0);
112        match self {
113            Self::Start | Self::Stretch => 0.0,
114            Self::Center => slack * 0.5,
115            Self::End => slack,
116        }
117    }
118
119    pub fn resolve(child: Option<Align>, container: Align) -> Align {
120        child.unwrap_or(container)
121    }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq)]
125pub struct ContainerStyle {
126    pub axis: Axis,
127
128    pub gap: f32,
129
130    pub cross_gap: f32,
131    pub align: Align,
132    pub justify: Justify,
133
134    pub wrap: bool,
135
136    pub align_content: Align,
137}
138
139impl Default for ContainerStyle {
140    fn default() -> Self {
141        Self {
142            axis: Axis::Horizontal,
143            gap: 0.0,
144            cross_gap: 0.0,
145            align: Align::Start,
146            justify: Justify::Start,
147            wrap: false,
148            align_content: Align::Start,
149        }
150    }
151}
152
153impl ContainerStyle {
154    pub fn new(axis: Axis, gap: f32) -> Self {
155        let gap = sanitize_gap(gap);
156        Self {
157            axis,
158            gap,
159            cross_gap: gap,
160            ..Self::default()
161        }
162    }
163
164    pub fn gap(mut self, gap: f32) -> Self {
165        let gap = sanitize_gap(gap);
166        self.gap = gap;
167        self.cross_gap = gap;
168        self
169    }
170
171    pub fn main_gap(mut self, gap: f32) -> Self {
172        self.gap = sanitize_gap(gap);
173        self
174    }
175
176    pub fn cross_gap(mut self, gap: f32) -> Self {
177        self.cross_gap = sanitize_gap(gap);
178        self
179    }
180
181    pub fn gaps(mut self, main: f32, cross: f32) -> Self {
182        self.gap = sanitize_gap(main);
183        self.cross_gap = sanitize_gap(cross);
184        self
185    }
186
187    pub fn align_items(mut self, align: Align) -> Self {
188        self.align = align;
189        self
190    }
191
192    pub fn justify_content(mut self, justify: Justify) -> Self {
193        self.justify = justify;
194        self
195    }
196
197    pub fn wrap(mut self, wrap: bool) -> Self {
198        self.wrap = wrap;
199        self
200    }
201
202    pub fn wrapped(mut self) -> Self {
203        self.wrap = true;
204        self
205    }
206
207    pub fn align_content(mut self, align: Align) -> Self {
208        self.align_content = align;
209        self
210    }
211
212    pub fn axis(mut self, axis: Axis) -> Self {
213        self.axis = axis;
214        self
215    }
216
217    pub fn main_gaps_total(self, count: usize) -> f32 {
218        if count > 1 {
219            self.gap * (count - 1) as f32
220        } else {
221            0.0
222        }
223    }
224}