Skip to main content

ply_engine/
layout.rs

1use crate::align::{AlignX, AlignY};
2use crate::engine;
3
4/// Per-corner border radius for rounded rectangles.
5#[derive(Debug, Clone, Copy, Default)]
6pub struct CornerRadius {
7    pub top_left: f32,
8    pub top_right: f32,
9    pub bottom_left: f32,
10    pub bottom_right: f32,
11}
12
13impl CornerRadius {
14    /// Returns `true` when all four corners have a radius of zero.
15    pub fn is_zero(&self) -> bool {
16        self.top_left == 0.0
17            && self.top_right == 0.0
18            && self.bottom_left == 0.0
19            && self.bottom_right == 0.0
20    }
21}
22
23impl From<f32> for CornerRadius {
24    /// Creates a corner radius with the same value for all corners.
25    fn from(value: f32) -> Self {
26        Self {
27            top_left: value,
28            top_right: value,
29            bottom_left: value,
30            bottom_right: value,
31        }
32    }
33}
34
35impl From<(f32, f32, f32, f32)> for CornerRadius {
36    /// Creates corner radii from a tuple in CSS order: (top-left, top-right, bottom-right, bottom-left).
37    fn from((tl, tr, br, bl): (f32, f32, f32, f32)) -> Self {
38        Self {
39            top_left: tl,
40            top_right: tr,
41            bottom_left: bl,
42            bottom_right: br,
43        }
44    }
45}
46
47/// Defines different sizing behaviors for an element.
48#[derive(Debug, Clone, Copy)]
49#[repr(u8)]
50pub enum SizingType {
51    /// The element's size is determined by its content and constrained by min/max values.
52    Fit,
53    /// The element expands to fill available space within min/max constraints.
54    Grow,
55    /// The element's size is fixed to a percentage of its parent.
56    Percent,
57    /// The element's size is set to a fixed value.
58    Fixed,
59}
60
61/// Represents different sizing strategies for layout elements.
62#[derive(Debug, Clone, Copy)]
63pub enum Sizing {
64    /// Fits the element’s width/height within a min and max constraint.
65    Fit(f32, f32),
66    /// Expands the element to fill available space within min/max constraints.
67    Grow(f32, f32),
68    /// Sets a fixed width/height.
69    Fixed(f32),
70    /// Sets width/height as a percentage of its parent. Value should be between `0.0` and `1.0`.
71    Percent(f32),
72}
73
74/// Converts a `Sizing` value into an engine `SizingAxis`.
75impl From<Sizing> for engine::SizingAxis {
76    fn from(value: Sizing) -> Self {
77        match value {
78            Sizing::Fit(min, max) => Self {
79                type_: engine::SizingType::Fit,
80                min_max: engine::SizingMinMax { min, max },
81                percent: 0.0,
82            },
83            Sizing::Grow(min, max) => Self {
84                type_: engine::SizingType::Grow,
85                min_max: engine::SizingMinMax { min, max },
86                percent: 0.0,
87            },
88            Sizing::Fixed(size) => Self {
89                type_: engine::SizingType::Fixed,
90                min_max: engine::SizingMinMax {
91                    min: size,
92                    max: size,
93                },
94                percent: 0.0,
95            },
96            Sizing::Percent(percent) => Self {
97                type_: engine::SizingType::Percent,
98                min_max: engine::SizingMinMax { min: 0.0, max: 0.0 },
99                percent,
100            },
101        }
102    }
103}
104
105/// Represents padding values for each side of an element.
106#[derive(Debug, Default)]
107pub struct Padding {
108    /// Padding on the left side.
109    pub left: u16,
110    /// Padding on the right side.
111    pub right: u16,
112    /// Padding on the top side.
113    pub top: u16,
114    /// Padding on the bottom side.
115    pub bottom: u16,
116}
117
118impl Padding {
119    /// Creates a new `Padding` with individual values for each side.
120    pub fn new(left: u16, right: u16, top: u16, bottom: u16) -> Self {
121        Self {
122            left,
123            right,
124            top,
125            bottom,
126        }
127    }
128
129    /// Sets the same padding value for all sides.
130    pub fn all(value: u16) -> Self {
131        Self::new(value, value, value, value)
132    }
133
134    /// Sets the same padding for left and right sides.
135    /// Top and bottom are set to `0`.
136    pub fn horizontal(value: u16) -> Self {
137        Self::new(value, value, 0, 0)
138    }
139
140    /// Sets the same padding for top and bottom sides.
141    /// Left and right are set to `0`.
142    pub fn vertical(value: u16) -> Self {
143        Self::new(0, 0, value, value)
144    }
145}
146
147impl From<u16> for Padding {
148    /// Creates padding with the same value for all sides.
149    fn from(value: u16) -> Self {
150        Self::all(value)
151    }
152}
153
154impl From<(u16, u16, u16, u16)> for Padding {
155    /// Creates padding from a tuple in CSS order: (top, right, bottom, left).
156    fn from((top, right, bottom, left): (u16, u16, u16, u16)) -> Self {
157        Self { left, right, top, bottom }
158    }
159}
160
161/// Defines the layout direction for arranging child elements.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
163#[repr(u8)]
164pub enum LayoutDirection {
165    /// Arranges elements from left to right.
166    #[default]
167    LeftToRight,
168    /// Arranges elements from top to bottom.
169    TopToBottom,
170}
171
172/// Builder for configuring layout properties using a closure.
173/// No lifetime parameters — works cleanly with closures.
174pub struct LayoutBuilder {
175    pub(crate) config: engine::LayoutConfig,
176}
177
178impl LayoutBuilder {
179    /// Sets the spacing between child elements.
180    #[inline]
181    pub fn gap(&mut self, gap: u16) -> &mut Self {
182        self.config.child_gap = gap;
183        self
184    }
185
186    /// Sets the alignment of child elements using separate X and Y values.
187    #[inline]
188    pub fn align(&mut self, x: AlignX, y: AlignY) -> &mut Self {
189        self.config.child_alignment.x = x;
190        self.config.child_alignment.y = y;
191        self
192    }
193
194    /// Sets the layout direction.
195    #[inline]
196    pub fn direction(&mut self, direction: LayoutDirection) -> &mut Self {
197        self.config.layout_direction = direction;
198        self
199    }
200
201    /// Sets padding values for the layout.
202    #[inline]
203    pub fn padding(&mut self, padding: impl Into<Padding>) -> &mut Self {
204        let padding = padding.into();
205        self.config.padding.left = padding.left;
206        self.config.padding.right = padding.right;
207        self.config.padding.top = padding.top;
208        self.config.padding.bottom = padding.bottom;
209        self
210    }
211}
212
213/// Shorthand macro for [`Sizing::Fit`]. Defaults max to `f32::MAX` if omitted.
214#[macro_export]
215macro_rules! fit {
216    ($min:expr, $max:expr) => {
217        $crate::layout::Sizing::Fit($min, $max)
218    };
219    ($min:expr) => {
220        fit!($min, f32::MAX)
221    };
222    () => {
223        fit!(0.0)
224    };
225}
226
227/// Shorthand macro for [`Sizing::Grow`]. Defaults max to `f32::MAX` if omitted.
228#[macro_export]
229macro_rules! grow {
230    ($min:expr, $max:expr) => {
231        $crate::layout::Sizing::Grow($min, $max)
232    };
233    ($min:expr) => {
234        grow!($min, f32::MAX)
235    };
236    () => {
237        grow!(0.0)
238    };
239}
240
241/// Shorthand macro for [`Sizing::Fixed`].
242#[macro_export]
243macro_rules! fixed {
244    ($val:expr) => {
245        $crate::layout::Sizing::Fixed($val)
246    };
247}
248
249/// Shorthand macro for [`Sizing::Percent`].
250/// The value has to be in range `0.0..=1.0`.
251#[macro_export]
252macro_rules! percent {
253    ($percent:expr) => {{
254        const _: () = assert!(
255            $percent >= 0.0 && $percent <= 1.0,
256            "Percent value must be between 0.0 and 1.0 inclusive!"
257        );
258        $crate::layout::Sizing::Percent($percent)
259    }};
260}
261
262#[cfg(test)]
263mod test {
264    use super::*;
265
266    #[test]
267    fn fit_macro() {
268        let both_args = fit!(12.0, 34.0);
269        assert!(matches!(both_args, Sizing::Fit(12.0, 34.0)));
270
271        let one_arg = fit!(12.0);
272        assert!(matches!(one_arg, Sizing::Fit(12.0, f32::MAX)));
273
274        let zero_args = fit!();
275        assert!(matches!(zero_args, Sizing::Fit(0.0, f32::MAX)));
276    }
277
278    #[test]
279    fn grow_macro() {
280        let both_args = grow!(12.0, 34.0);
281        assert!(matches!(both_args, Sizing::Grow(12.0, 34.0)));
282
283        let one_arg = grow!(12.0);
284        assert!(matches!(one_arg, Sizing::Grow(12.0, f32::MAX)));
285
286        let zero_args = grow!();
287        assert!(matches!(zero_args, Sizing::Grow(0.0, f32::MAX)));
288    }
289
290    #[test]
291    fn fixed_macro() {
292        let value = fixed!(123.0);
293        assert!(matches!(value, Sizing::Fixed(123.0)));
294    }
295
296    #[test]
297    fn percent_macro() {
298        let value = percent!(0.5);
299        assert!(matches!(value, Sizing::Percent(0.5)));
300    }
301}