Skip to main content

agpu/layout/
flex.rs

1//! Flexbox layout — row/column with alignment, wrapping, and spacing.
2
3use crate::core::Rect;
4use crate::layout::{DesiredSize, Layout, SizeConstraints};
5
6/// Main axis direction.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Align {
9    /// Lay out children in a horizontal row.
10    Row,
11    /// Lay out children in a vertical column.
12    Column,
13}
14
15/// How to distribute remaining space on the main axis.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum JustifyContent {
18    Start,
19    End,
20    Center,
21    SpaceBetween,
22    SpaceAround,
23    SpaceEvenly,
24}
25
26/// Cross-axis alignment.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum CrossAlign {
29    Start,
30    End,
31    Center,
32    Stretch,
33}
34
35/// Wrapping behaviour.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum FlexWrap {
38    NoWrap,
39    Wrap,
40}
41
42/// Per-child flex configuration.
43#[derive(Debug, Clone, Copy)]
44pub struct FlexItem {
45    pub grow: f32,
46    pub shrink: f32,
47    pub basis: Option<f32>,
48}
49
50impl Default for FlexItem {
51    fn default() -> Self {
52        Self {
53            grow: 0.0,
54            shrink: 1.0,
55            basis: None,
56        }
57    }
58}
59
60impl FlexItem {
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    pub fn grow(mut self, grow: f32) -> Self {
66        self.grow = grow;
67        self
68    }
69}
70
71/// Flexbox layout container.
72pub struct FlexLayout {
73    direction: Align,
74    justify: JustifyContent,
75    cross_align: CrossAlign,
76    wrap: FlexWrap,
77    spacing: f32,
78}
79
80impl FlexLayout {
81    pub fn row() -> Self {
82        Self {
83            direction: Align::Row,
84            justify: JustifyContent::Start,
85            cross_align: CrossAlign::Start,
86            wrap: FlexWrap::NoWrap,
87            spacing: 0.0,
88        }
89    }
90
91    pub fn column() -> Self {
92        Self {
93            direction: Align::Column,
94            justify: JustifyContent::Start,
95            cross_align: CrossAlign::Start,
96            wrap: FlexWrap::NoWrap,
97            spacing: 0.0,
98        }
99    }
100
101    pub fn spacing(mut self, spacing: f32) -> Self {
102        self.spacing = spacing;
103        self
104    }
105
106    pub fn justify(mut self, justify: JustifyContent) -> Self {
107        self.justify = justify;
108        self
109    }
110
111    pub fn cross_align(mut self, cross_align: CrossAlign) -> Self {
112        self.cross_align = cross_align;
113        self
114    }
115
116    pub fn wrap(mut self, wrap: FlexWrap) -> Self {
117        self.wrap = wrap;
118        self
119    }
120}
121
122impl Layout for FlexLayout {
123    fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize {
124        if children.is_empty() {
125            return DesiredSize::zero();
126        }
127        let total_spacing = self.spacing * (children.len() as f32 - 1.0).max(0.0);
128        match self.direction {
129            Align::Row => {
130                let width: f32 = children.iter().map(|c| c.width).sum::<f32>() + total_spacing;
131                let height = children.iter().map(|c| c.height).fold(0.0f32, f32::max);
132                let (w, h) = constraints.clamp(width, height);
133                DesiredSize::new(w, h)
134            }
135            Align::Column => {
136                let width = children.iter().map(|c| c.width).fold(0.0f32, f32::max);
137                let height: f32 = children.iter().map(|c| c.height).sum::<f32>() + total_spacing;
138                let (w, h) = constraints.clamp(width, height);
139                DesiredSize::new(w, h)
140            }
141        }
142    }
143
144    fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
145        if children.is_empty() {
146            return vec![];
147        }
148
149        if self.wrap == FlexWrap::Wrap {
150            return self.arrange_wrapped(children, area);
151        }
152
153        let mut rects = Vec::with_capacity(children.len());
154        match self.direction {
155            Align::Row => {
156                let total_child_width: f32 = children.iter().map(|c| c.width).sum();
157                let total_spacing = self.spacing * (children.len() as f32 - 1.0).max(0.0);
158                let remaining = (area.width - total_child_width - total_spacing).max(0.0);
159                let (start_offset, inter_spacing) = self.justify_offsets(remaining, children.len());
160
161                let mut x = area.x + start_offset;
162                for child in children {
163                    let y = self.cross_offset(area.y, area.height, child.height);
164                    let h = if self.cross_align == CrossAlign::Stretch {
165                        area.height
166                    } else {
167                        child.height
168                    };
169                    rects.push(Rect::new(x, y, child.width, h));
170                    x += child.width + self.spacing + inter_spacing;
171                }
172            }
173            Align::Column => {
174                let total_child_height: f32 = children.iter().map(|c| c.height).sum();
175                let total_spacing = self.spacing * (children.len() as f32 - 1.0).max(0.0);
176                let remaining = (area.height - total_child_height - total_spacing).max(0.0);
177                let (start_offset, inter_spacing) = self.justify_offsets(remaining, children.len());
178
179                let mut y = area.y + start_offset;
180                for child in children {
181                    let x = self.cross_offset(area.x, area.width, child.width);
182                    let w = if self.cross_align == CrossAlign::Stretch {
183                        area.width
184                    } else {
185                        child.width
186                    };
187                    rects.push(Rect::new(x, y, w, child.height));
188                    y += child.height + self.spacing + inter_spacing;
189                }
190            }
191        }
192        rects
193    }
194}
195
196impl FlexLayout {
197    fn justify_offsets(&self, remaining: f32, count: usize) -> (f32, f32) {
198        match self.justify {
199            JustifyContent::Start => (0.0, 0.0),
200            JustifyContent::End => (remaining, 0.0),
201            JustifyContent::Center => (remaining / 2.0, 0.0),
202            JustifyContent::SpaceBetween => {
203                if count <= 1 {
204                    (0.0, 0.0)
205                } else {
206                    (0.0, remaining / (count as f32 - 1.0))
207                }
208            }
209            JustifyContent::SpaceAround => {
210                let gap = remaining / count as f32;
211                (gap / 2.0, gap)
212            }
213            JustifyContent::SpaceEvenly => {
214                let gap = remaining / (count as f32 + 1.0);
215                (gap, gap)
216            }
217        }
218    }
219
220    fn cross_offset(&self, area_start: f32, area_size: f32, child_size: f32) -> f32 {
221        match self.cross_align {
222            CrossAlign::Start | CrossAlign::Stretch => area_start,
223            CrossAlign::End => area_start + area_size - child_size,
224            CrossAlign::Center => area_start + (area_size - child_size) / 2.0,
225        }
226    }
227
228    fn arrange_wrapped(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect> {
229        let mut rects = vec![Rect::new(0.0, 0.0, 0.0, 0.0); children.len()];
230
231        match self.direction {
232            Align::Row => {
233                let mut x = area.x;
234                let mut y = area.y;
235                let mut line_height: f32 = 0.0;
236
237                for (i, child) in children.iter().enumerate() {
238                    if x + child.width > area.x + area.width && i > 0 {
239                        x = area.x;
240                        y += line_height + self.spacing;
241                        line_height = 0.0;
242                    }
243                    rects[i] = Rect::new(x, y, child.width, child.height);
244                    x += child.width + self.spacing;
245                    line_height = line_height.max(child.height);
246                }
247            }
248            Align::Column => {
249                let mut x = area.x;
250                let mut y = area.y;
251                let mut col_width: f32 = 0.0;
252
253                for (i, child) in children.iter().enumerate() {
254                    if y + child.height > area.y + area.height && i > 0 {
255                        y = area.y;
256                        x += col_width + self.spacing;
257                        col_width = 0.0;
258                    }
259                    rects[i] = Rect::new(x, y, child.width, child.height);
260                    y += child.height + self.spacing;
261                    col_width = col_width.max(child.width);
262                }
263            }
264        }
265        rects
266    }
267}