1use crate::align::{AlignX, AlignY};
2use crate::engine;
3
4#[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 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 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 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#[derive(Debug, Clone, Copy)]
49#[repr(u8)]
50pub enum SizingType {
51 Fit,
53 Grow,
55 Percent,
57 Fixed,
59}
60
61#[derive(Debug, Clone, Copy)]
63pub enum Sizing {
64 Fit(f32, f32),
66 Grow(f32, f32, f32),
71 Fixed(f32),
73 Percent(f32),
75}
76
77impl From<Sizing> for engine::SizingAxis {
79 fn from(value: Sizing) -> Self {
80 match value {
81 Sizing::Fit(min, max) => Self {
82 type_: engine::SizingType::Fit,
83 min_max: engine::SizingMinMax { min, max },
84 percent: 0.0,
85 grow_weight: 1.0,
86 },
87 Sizing::Grow(min, max, weight) => {
88 assert!(weight >= 0.0, "Grow weight must be non-negative.");
89
90 if weight == 0.0 {
91 Self {
92 type_: engine::SizingType::Fit,
93 min_max: engine::SizingMinMax { min, max },
94 percent: 0.0,
95 grow_weight: 1.0,
96 }
97 } else {
98 Self {
99 type_: engine::SizingType::Grow,
100 min_max: engine::SizingMinMax { min, max },
101 percent: 0.0,
102 grow_weight: weight,
103 }
104 }
105 }
106 Sizing::Fixed(size) => Self {
107 type_: engine::SizingType::Fixed,
108 min_max: engine::SizingMinMax {
109 min: size,
110 max: size,
111 },
112 percent: 0.0,
113 grow_weight: 1.0,
114 },
115 Sizing::Percent(percent) => Self {
116 type_: engine::SizingType::Percent,
117 min_max: engine::SizingMinMax { min: 0.0, max: 0.0 },
118 percent,
119 grow_weight: 1.0,
120 },
121 }
122 }
123}
124
125#[derive(Debug, Default)]
127pub struct Padding {
128 pub left: u16,
130 pub right: u16,
132 pub top: u16,
134 pub bottom: u16,
136}
137
138impl Padding {
139 pub fn new(left: u16, right: u16, top: u16, bottom: u16) -> Self {
141 Self {
142 left,
143 right,
144 top,
145 bottom,
146 }
147 }
148
149 pub fn all(value: u16) -> Self {
151 Self::new(value, value, value, value)
152 }
153
154 pub fn horizontal(value: u16) -> Self {
157 Self::new(value, value, 0, 0)
158 }
159
160 pub fn vertical(value: u16) -> Self {
163 Self::new(0, 0, value, value)
164 }
165}
166
167impl From<u16> for Padding {
168 fn from(value: u16) -> Self {
170 Self::all(value)
171 }
172}
173
174impl From<(u16, u16, u16, u16)> for Padding {
175 fn from((top, right, bottom, left): (u16, u16, u16, u16)) -> Self {
177 Self { left, right, top, bottom }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
183#[repr(u8)]
184pub enum LayoutDirection {
185 #[default]
187 LeftToRight,
188 TopToBottom,
190}
191
192pub struct LayoutBuilder {
195 pub(crate) config: engine::LayoutConfig,
196}
197
198impl LayoutBuilder {
199 #[inline]
201 pub fn gap(&mut self, gap: u16) -> &mut Self {
202 self.config.child_gap = gap;
203 self
204 }
205
206 #[inline]
208 pub fn align(&mut self, x: AlignX, y: AlignY) -> &mut Self {
209 self.config.child_alignment.x = x;
210 self.config.child_alignment.y = y;
211 self
212 }
213
214 #[inline]
216 pub fn direction(&mut self, direction: LayoutDirection) -> &mut Self {
217 self.config.layout_direction = direction;
218 self
219 }
220
221 #[inline]
223 pub fn wrap(&mut self) -> &mut Self {
224 self.config.wrap = true;
225 self
226 }
227
228 #[inline]
230 pub fn wrap_gap(&mut self, gap: u16) -> &mut Self {
231 self.config.wrap_gap = gap;
232 self
233 }
234
235 #[inline]
237 pub fn padding(&mut self, padding: impl Into<Padding>) -> &mut Self {
238 let padding = padding.into();
239 self.config.padding.left = padding.left;
240 self.config.padding.right = padding.right;
241 self.config.padding.top = padding.top;
242 self.config.padding.bottom = padding.bottom;
243 self
244 }
245}
246
247#[macro_export]
249macro_rules! fit {
250 ($min:expr, $max:expr) => {
251 $crate::layout::Sizing::Fit($min, $max)
252 };
253 ($min:expr) => {
254 fit!($min, f32::MAX)
255 };
256 () => {
257 fit!(0.0)
258 };
259
260 ($($name:ident : $value:expr),+ $(,)?) => {
261 $crate::fit!(@named (0.0, f32::MAX); $($name : $value,)+)
262 };
263
264 (@named ($min:expr, $max:expr); ) => {
265 $crate::layout::Sizing::Fit($min, $max)
266 };
267 (@named ($min:expr, $max:expr); min : $value:expr, $($rest:tt)*) => {
268 $crate::fit!(@named ($value, $max); $($rest)*)
269 };
270 (@named ($min:expr, $max:expr); max : $value:expr, $($rest:tt)*) => {
271 $crate::fit!(@named ($min, $value); $($rest)*)
272 };
273 (@named ($min:expr, $max:expr); $unknown:ident : $value:expr, $($rest:tt)*) => {
274 compile_error!("Unknown named argument for fit!(). Expected: min, max.");
275 };
276
277 ($first:expr, $($rest:tt)+) => {
278 compile_error!("Do not mix positional and named arguments in fit!().");
279 };
280}
281
282#[macro_export]
284macro_rules! grow {
285 ($min:expr, $max:expr, $weight:expr) => {
286 $crate::layout::Sizing::Grow($min, $max, $weight)
287 };
288 ($min:expr, $max:expr) => {
289 grow!($min, $max, 1.0)
290 };
291 ($min:expr) => {
292 grow!($min, f32::MAX)
293 };
294 () => {
295 grow!(0.0)
296 };
297
298 ($($name:ident : $value:expr),+ $(,)?) => {
299 $crate::grow!(@named (0.0, f32::MAX, 1.0); $($name : $value,)+)
300 };
301
302 (@named ($min:expr, $max:expr, $weight:expr); ) => {
303 $crate::layout::Sizing::Grow($min, $max, $weight)
304 };
305 (@named ($min:expr, $max:expr, $weight:expr); min : $value:expr, $($rest:tt)*) => {
306 $crate::grow!(@named ($value, $max, $weight); $($rest)*)
307 };
308 (@named ($min:expr, $max:expr, $weight:expr); max : $value:expr, $($rest:tt)*) => {
309 $crate::grow!(@named ($min, $value, $weight); $($rest)*)
310 };
311 (@named ($min:expr, $max:expr, $weight:expr); weight : $value:expr, $($rest:tt)*) => {
312 $crate::grow!(@named ($min, $max, $value); $($rest)*)
313 };
314 (@named ($min:expr, $max:expr, $weight:expr); $unknown:ident : $value:expr, $($rest:tt)*) => {
315 compile_error!("Unknown named argument for grow!(). Expected: min, max, weight.");
316 };
317}
318
319#[macro_export]
321macro_rules! fixed {
322 ($val:expr) => {
323 $crate::layout::Sizing::Fixed($val)
324 };
325}
326
327#[macro_export]
330macro_rules! percent {
331 ($percent:expr) => {{
332 const _: () = assert!(
333 $percent >= 0.0 && $percent <= 1.0,
334 "Percent value must be between 0.0 and 1.0 inclusive!"
335 );
336 $crate::layout::Sizing::Percent($percent)
337 }};
338}
339
340#[cfg(test)]
341mod test {
342 use super::*;
343
344 #[test]
345 fn fit_macro() {
346 let both_args = fit!(12.0, 34.0);
347 assert!(matches!(both_args, Sizing::Fit(12.0, 34.0)));
348
349 let one_arg = fit!(12.0);
350 assert!(matches!(one_arg, Sizing::Fit(12.0, f32::MAX)));
351
352 let zero_args = fit!();
353 assert!(matches!(zero_args, Sizing::Fit(0.0, f32::MAX)));
354
355 let named_max = fit!(max: 34.0);
356 assert!(matches!(named_max, Sizing::Fit(0.0, 34.0)));
357
358 let named_min = fit!(min: 12.0);
359 assert!(matches!(named_min, Sizing::Fit(12.0, f32::MAX)));
360
361 let named_both = fit!(max: 34.0, min: 12.0);
362 assert!(matches!(named_both, Sizing::Fit(12.0, 34.0)));
363 }
364
365 #[test]
366 fn grow_macro() {
367 let three_args = grow!(12.0, 34.0, 2.5);
368 assert!(matches!(three_args, Sizing::Grow(12.0, 34.0, 2.5)));
369
370 let both_args = grow!(12.0, 34.0);
371 assert!(matches!(both_args, Sizing::Grow(12.0, 34.0, 1.0)));
372
373 let one_arg = grow!(12.0);
374 assert!(matches!(one_arg, Sizing::Grow(12.0, f32::MAX, 1.0)));
375
376 let zero_args = grow!();
377 assert!(matches!(zero_args, Sizing::Grow(0.0, f32::MAX, 1.0)));
378
379 let named_weight = grow!(weight: 2.0);
380 assert!(matches!(named_weight, Sizing::Grow(0.0, f32::MAX, 2.0)));
381
382 let named_min_weight = grow!(min: 12.0, weight: 2.0);
383 assert!(matches!(named_min_weight, Sizing::Grow(12.0, f32::MAX, 2.0)));
384
385 let named_max_weight = grow!(max: 34.0, weight: 3.0);
386 assert!(matches!(named_max_weight, Sizing::Grow(0.0, 34.0, 3.0)));
387
388 let named_all = grow!(weight: 2.0, max: 34.0, min: 12.0);
389 assert!(matches!(named_all, Sizing::Grow(12.0, 34.0, 2.0)));
390 }
391
392 #[test]
393 fn zero_weight_grow_converts_to_fit_axis() {
394 let axis: engine::SizingAxis = grow!(12.0, 34.0, 0.0).into();
395 assert_eq!(axis.type_, engine::SizingType::Fit);
396 assert_eq!(axis.min_max.min, 12.0);
397 assert_eq!(axis.min_max.max, 34.0);
398 }
399
400 #[test]
401 #[should_panic(expected = "Grow weight must be non-negative.")]
402 fn negative_grow_weight_panics() {
403 let _axis: engine::SizingAxis = grow!(0.0, f32::MAX, -1.0).into();
404 }
405
406 #[test]
407 fn fixed_macro() {
408 let value = fixed!(123.0);
409 assert!(matches!(value, Sizing::Fixed(123.0)));
410 }
411
412 #[test]
413 fn percent_macro() {
414 let value = percent!(0.5);
415 assert!(matches!(value, Sizing::Percent(0.5)));
416 }
417}