1
//! CSS properties for `margin`, `padding`, and `gap` (column-gap / row-gap).
2
//!
3
//! Shorthand parsers (`parse_layout_padding`, `parse_layout_margin`) and
4
//! longhand per-side parsers are gated behind `#[cfg(feature = "parser")]`.
5

            
6
use alloc::{
7
    string::{String, ToString},
8
    vec::Vec,
9
};
10

            
11
#[cfg(feature = "parser")]
12
use crate::props::basic::pixel::{parse_pixel_value_with_auto, PixelValueWithAuto};
13
use crate::{
14
    css::PrintAsCssValue,
15
    props::{
16
        basic::pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
17
        macros::PixelValueTaker,
18
    },
19
};
20

            
21
// --- TYPE DEFINITIONS ---
22

            
23
// Spacing properties - wrapper structs around PixelValue for type safety
24

            
25
macro_rules! impl_spacing_type_impls {
26
    ($name:ident) => {
27
        impl ::core::fmt::Debug for $name {
28
152
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
29
152
                write!(f, "{}", self.inner)
30
152
            }
31
        }
32

            
33
        impl PixelValueTaker for $name {
34
            fn from_pixel_value(inner: PixelValue) -> Self {
35
                Self { inner }
36
            }
37
        }
38

            
39
        impl_pixel_value!($name);
40
    };
41
}
42

            
43
/// Layout padding top value
44
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45
#[repr(C)]
46
pub struct LayoutPaddingTop {
47
    pub inner: PixelValue,
48
}
49
impl_spacing_type_impls!(LayoutPaddingTop);
50

            
51
/// Layout padding right value
52
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53
#[repr(C)]
54
pub struct LayoutPaddingRight {
55
    pub inner: PixelValue,
56
}
57
impl_spacing_type_impls!(LayoutPaddingRight);
58

            
59
/// Layout padding bottom value
60
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61
#[repr(C)]
62
pub struct LayoutPaddingBottom {
63
    pub inner: PixelValue,
64
}
65
impl_spacing_type_impls!(LayoutPaddingBottom);
66

            
67
/// Layout padding left value
68
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69
#[repr(C)]
70
pub struct LayoutPaddingLeft {
71
    pub inner: PixelValue,
72
}
73
impl_spacing_type_impls!(LayoutPaddingLeft);
74

            
75
/// Layout padding inline start value (for RTL/LTR support)
76
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77
#[repr(C)]
78
pub struct LayoutPaddingInlineStart {
79
    pub inner: PixelValue,
80
}
81
impl_spacing_type_impls!(LayoutPaddingInlineStart);
82

            
83
/// Layout padding inline end value (for RTL/LTR support)
84
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85
#[repr(C)]
86
pub struct LayoutPaddingInlineEnd {
87
    pub inner: PixelValue,
88
}
89
impl_spacing_type_impls!(LayoutPaddingInlineEnd);
90

            
91
/// Layout margin top value
92
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93
#[repr(C)]
94
pub struct LayoutMarginTop {
95
    pub inner: PixelValue,
96
}
97
impl_spacing_type_impls!(LayoutMarginTop);
98

            
99
/// Layout margin right value
100
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101
#[repr(C)]
102
pub struct LayoutMarginRight {
103
    pub inner: PixelValue,
104
}
105
impl_spacing_type_impls!(LayoutMarginRight);
106

            
107
/// Layout margin bottom value
108
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
109
#[repr(C)]
110
pub struct LayoutMarginBottom {
111
    pub inner: PixelValue,
112
}
113
impl_spacing_type_impls!(LayoutMarginBottom);
114

            
115
/// Layout margin left value
116
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117
#[repr(C)]
118
pub struct LayoutMarginLeft {
119
    pub inner: PixelValue,
120
}
121
impl_spacing_type_impls!(LayoutMarginLeft);
122

            
123
/// Layout column gap value (for flexbox/grid)
124
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125
#[repr(C)]
126
pub struct LayoutColumnGap {
127
    pub inner: PixelValue,
128
}
129
impl_spacing_type_impls!(LayoutColumnGap);
130

            
131
/// Layout row gap value (for flexbox/grid)
132
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133
#[repr(C)]
134
pub struct LayoutRowGap {
135
    pub inner: PixelValue,
136
}
137
impl_spacing_type_impls!(LayoutRowGap);
138

            
139
// --- PARSERS ---
140

            
141
#[cfg(feature = "parser")]
142
macro_rules! impl_spacing_parse_error {
143
    ($borrowed:ident, $owned:ident, $property_name:expr) => {
144
        #[cfg(feature = "parser")]
145
        impl_debug_as_display!($borrowed<'a>);
146

            
147
        #[cfg(feature = "parser")]
148
        impl_display! { $borrowed<'a>, {
149
            PixelValueParseError(e) => format!("Could not parse pixel value: {}", e),
150
            TooManyValues => concat!("Too many values: ", $property_name, " property accepts at most 4 values."),
151
            TooFewValues => concat!("Too few values: ", $property_name, " property requires at least 1 value."),
152
        }}
153

            
154
        #[cfg(feature = "parser")]
155
        impl_from!(
156
            CssPixelValueParseError<'a>,
157
            $borrowed::PixelValueParseError
158
        );
159

            
160
        #[cfg(feature = "parser")]
161
        impl<'a> $borrowed<'a> {
162
            pub fn to_contained(&self) -> $owned {
163
                match self {
164
                    $borrowed::PixelValueParseError(e) => {
165
                        $owned::PixelValueParseError(e.to_contained())
166
                    }
167
                    $borrowed::TooManyValues => $owned::TooManyValues,
168
                    $borrowed::TooFewValues => $owned::TooFewValues,
169
                }
170
            }
171
        }
172

            
173
        #[cfg(feature = "parser")]
174
        impl $owned {
175
            pub fn to_shared<'a>(&'a self) -> $borrowed<'a> {
176
                match self {
177
                    $owned::PixelValueParseError(e) => {
178
                        $borrowed::PixelValueParseError(e.to_shared())
179
                    }
180
                    $owned::TooManyValues => $borrowed::TooManyValues,
181
                    $owned::TooFewValues => $borrowed::TooFewValues,
182
                }
183
            }
184
        }
185
    };
186
}
187

            
188
// -- Padding Shorthand Parser --
189

            
190
/// Error from parsing a CSS `padding` shorthand value.
191
#[cfg(feature = "parser")]
192
#[derive(Clone, PartialEq)]
193
pub enum LayoutPaddingParseError<'a> {
194
    PixelValueParseError(CssPixelValueParseError<'a>),
195
    TooManyValues,
196
    TooFewValues,
197
}
198

            
199
/// Owned variant of [`LayoutPaddingParseError`].
200
#[cfg(feature = "parser")]
201
#[derive(Debug, Clone, PartialEq)]
202
#[repr(C, u8)]
203
pub enum LayoutPaddingParseErrorOwned {
204
    PixelValueParseError(CssPixelValueParseErrorOwned),
205
    TooManyValues,
206
    TooFewValues,
207
}
208

            
209
#[cfg(feature = "parser")]
210
impl_spacing_parse_error!(LayoutPaddingParseError, LayoutPaddingParseErrorOwned, "padding");
211

            
212
/// Result of parsing the CSS `padding` shorthand property (1–4 values).
213
#[cfg(feature = "parser")]
214
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215
pub struct LayoutPadding {
216
    pub top: PixelValueWithAuto,
217
    pub bottom: PixelValueWithAuto,
218
    pub left: PixelValueWithAuto,
219
    pub right: PixelValueWithAuto,
220
}
221

            
222
#[cfg(feature = "parser")]
223
8710
pub fn parse_layout_padding<'a>(
224
8710
    input: &'a str,
225
8710
) -> Result<LayoutPadding, LayoutPaddingParseError<'a>> {
226
8710
    let values: Vec<_> = input.split_whitespace().collect();
227

            
228
8710
    let parsed_values: Vec<PixelValueWithAuto> = values
229
8710
        .iter()
230
8894
        .map(|s| parse_pixel_value_with_auto(s))
231
8710
        .collect::<Result<_, _>>()?;
232

            
233
8708
    match parsed_values.len() {
234
        1 => {
235
            // top, right, bottom, left
236
8573
            let all = parsed_values[0];
237
8573
            Ok(LayoutPadding {
238
8573
                top: all,
239
8573
                right: all,
240
8573
                bottom: all,
241
8573
                left: all,
242
8573
            })
243
        }
244
        2 => {
245
            // top/bottom, left/right
246
101
            let vertical = parsed_values[0];
247
101
            let horizontal = parsed_values[1];
248
101
            Ok(LayoutPadding {
249
101
                top: vertical,
250
101
                right: horizontal,
251
101
                bottom: vertical,
252
101
                left: horizontal,
253
101
            })
254
        }
255
        3 => {
256
            // top, left/right, bottom
257
15
            let top = parsed_values[0];
258
15
            let horizontal = parsed_values[1];
259
15
            let bottom = parsed_values[2];
260
15
            Ok(LayoutPadding {
261
15
                top,
262
15
                right: horizontal,
263
15
                bottom,
264
15
                left: horizontal,
265
15
            })
266
        }
267
        4 => {
268
            // top, right, bottom, left
269
15
            Ok(LayoutPadding {
270
15
                top: parsed_values[0],
271
15
                right: parsed_values[1],
272
15
                bottom: parsed_values[2],
273
15
                left: parsed_values[3],
274
15
            })
275
        }
276
2
        0 => Err(LayoutPaddingParseError::TooFewValues),
277
2
        _ => Err(LayoutPaddingParseError::TooManyValues),
278
    }
279
8710
}
280

            
281
// -- Margin Shorthand Parser --
282

            
283
/// Error from parsing a CSS `margin` shorthand value.
284
#[cfg(feature = "parser")]
285
#[derive(Clone, PartialEq)]
286
pub enum LayoutMarginParseError<'a> {
287
    PixelValueParseError(CssPixelValueParseError<'a>),
288
    TooManyValues,
289
    TooFewValues,
290
}
291

            
292
/// Owned variant of [`LayoutMarginParseError`].
293
#[cfg(feature = "parser")]
294
#[derive(Debug, Clone, PartialEq)]
295
#[repr(C, u8)]
296
pub enum LayoutMarginParseErrorOwned {
297
    PixelValueParseError(CssPixelValueParseErrorOwned),
298
    TooManyValues,
299
    TooFewValues,
300
}
301

            
302
#[cfg(feature = "parser")]
303
impl_spacing_parse_error!(LayoutMarginParseError, LayoutMarginParseErrorOwned, "margin");
304

            
305
/// Result of parsing the CSS `margin` shorthand property (1–4 values).
306
#[cfg(feature = "parser")]
307
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
308
pub struct LayoutMargin {
309
    pub top: PixelValueWithAuto,
310
    pub bottom: PixelValueWithAuto,
311
    pub left: PixelValueWithAuto,
312
    pub right: PixelValueWithAuto,
313
}
314

            
315
#[cfg(feature = "parser")]
316
4464
pub fn parse_layout_margin<'a>(input: &'a str) -> Result<LayoutMargin, LayoutMarginParseError<'a>> {
317
    // Margin parsing logic is identical to padding, so we can reuse the padding parser
318
    // and just map the Ok and Err variants to the margin-specific types.
319
4464
    match parse_layout_padding(input) {
320
4461
        Ok(padding) => Ok(LayoutMargin {
321
4461
            top: padding.top,
322
4461
            left: padding.left,
323
4461
            right: padding.right,
324
4461
            bottom: padding.bottom,
325
4461
        }),
326
3
        Err(e) => match e {
327
1
            LayoutPaddingParseError::PixelValueParseError(err) => {
328
1
                Err(LayoutMarginParseError::PixelValueParseError(err))
329
            }
330
1
            LayoutPaddingParseError::TooManyValues => Err(LayoutMarginParseError::TooManyValues),
331
1
            LayoutPaddingParseError::TooFewValues => Err(LayoutMarginParseError::TooFewValues),
332
        },
333
    }
334
4464
}
335

            
336
// -- Longhand Property Parsers --
337

            
338
macro_rules! typed_pixel_value_parser {
339
    (
340
        $fn:ident, $fn_str:expr, $return:ident, $return_str:expr, $import_str:expr, $test_str:expr
341
    ) => {
342
        ///Parses a `
343
        #[doc = $return_str]
344
        ///` attribute from a `&str`
345
        ///
346
        ///# Example
347
        ///
348
        ///```rust
349
        #[doc = $import_str]
350
        #[doc = $test_str]
351
        ///```
352
1956
        pub fn $fn<'a>(input: &'a str) -> Result<$return, CssPixelValueParseError<'a>> {
353
1956
            crate::props::basic::parse_pixel_value(input).map(|e| $return { inner: e })
354
1956
        }
355

            
356
        impl crate::props::formatter::FormatAsCssValue for $return {
357
            fn format_as_css_value(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
358
                self.inner.format_as_css_value(f)
359
            }
360
        }
361
    };
362
    ($fn:ident, $return:ident) => {
363
        typed_pixel_value_parser!(
364
            $fn,
365
            stringify!($fn),
366
            $return,
367
            stringify!($return),
368
            concat!(
369
                "# extern crate azul_css;",
370
                "\r\n",
371
                "# use azul_css::props::layout::spacing::",
372
                stringify!($fn),
373
                ";",
374
                "\r\n",
375
                "# use azul_css::props::basic::pixel::PixelValue;\r\n",
376
                "# use azul_css::props::layout::spacing::",
377
                stringify!($return),
378
                ";\r\n"
379
            ),
380
            concat!(
381
                "assert_eq!(",
382
                stringify!($fn),
383
                "(\"5px\"), Ok(",
384
                stringify!($return),
385
                " { inner: PixelValue::px(5.0) }));"
386
            )
387
        );
388
    };
389
}
390

            
391
#[cfg(feature = "parser")]
392
typed_pixel_value_parser!(parse_layout_padding_top, LayoutPaddingTop);
393
#[cfg(feature = "parser")]
394
typed_pixel_value_parser!(parse_layout_padding_right, LayoutPaddingRight);
395
#[cfg(feature = "parser")]
396
typed_pixel_value_parser!(parse_layout_padding_bottom, LayoutPaddingBottom);
397
#[cfg(feature = "parser")]
398
typed_pixel_value_parser!(parse_layout_padding_left, LayoutPaddingLeft);
399
#[cfg(feature = "parser")]
400
typed_pixel_value_parser!(parse_layout_padding_inline_start, LayoutPaddingInlineStart);
401
#[cfg(feature = "parser")]
402
typed_pixel_value_parser!(parse_layout_padding_inline_end, LayoutPaddingInlineEnd);
403

            
404
#[cfg(feature = "parser")]
405
typed_pixel_value_parser!(parse_layout_margin_top, LayoutMarginTop);
406
#[cfg(feature = "parser")]
407
typed_pixel_value_parser!(parse_layout_margin_right, LayoutMarginRight);
408
#[cfg(feature = "parser")]
409
typed_pixel_value_parser!(parse_layout_margin_bottom, LayoutMarginBottom);
410
#[cfg(feature = "parser")]
411
typed_pixel_value_parser!(parse_layout_margin_left, LayoutMarginLeft);
412

            
413
#[cfg(feature = "parser")]
414
typed_pixel_value_parser!(parse_layout_column_gap, LayoutColumnGap);
415
#[cfg(feature = "parser")]
416
typed_pixel_value_parser!(parse_layout_row_gap, LayoutRowGap);
417

            
418
#[cfg(all(test, feature = "parser"))]
419
mod tests {
420
    use super::*;
421
    use crate::props::basic::pixel::{PixelValue, PixelValueWithAuto};
422

            
423
    #[test]
424
1
    fn test_parse_layout_padding_shorthand() {
425
        // 1 value
426
1
        let result = parse_layout_padding("10px").unwrap();
427
1
        assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
428
1
        assert_eq!(
429
            result.right,
430
1
            PixelValueWithAuto::Exact(PixelValue::px(10.0))
431
        );
432
1
        assert_eq!(
433
            result.bottom,
434
1
            PixelValueWithAuto::Exact(PixelValue::px(10.0))
435
        );
436
1
        assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
437

            
438
        // 2 values
439
1
        let result = parse_layout_padding("5% 2em").unwrap();
440
1
        assert_eq!(
441
            result.top,
442
1
            PixelValueWithAuto::Exact(PixelValue::percent(5.0))
443
        );
444
1
        assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
445
1
        assert_eq!(
446
            result.bottom,
447
1
            PixelValueWithAuto::Exact(PixelValue::percent(5.0))
448
        );
449
1
        assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
450

            
451
        // 3 values
452
1
        let result = parse_layout_padding("1px 2px 3px").unwrap();
453
1
        assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
454
1
        assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
455
1
        assert_eq!(
456
            result.bottom,
457
1
            PixelValueWithAuto::Exact(PixelValue::px(3.0))
458
        );
459
1
        assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
460

            
461
        // 4 values
462
1
        let result = parse_layout_padding("1px 2px 3px 4px").unwrap();
463
1
        assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
464
1
        assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
465
1
        assert_eq!(
466
            result.bottom,
467
1
            PixelValueWithAuto::Exact(PixelValue::px(3.0))
468
        );
469
1
        assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(4.0)));
470

            
471
        // Whitespace
472
1
        let result = parse_layout_padding("  1px   2px  ").unwrap();
473
1
        assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
474
1
        assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
475
1
    }
476

            
477
    #[test]
478
1
    fn test_parse_layout_padding_errors() {
479
1
        assert!(matches!(
480
1
            parse_layout_padding("").err().unwrap(),
481
            LayoutPaddingParseError::TooFewValues
482
        ));
483
1
        assert!(matches!(
484
1
            parse_layout_padding("1px 2px 3px 4px 5px").err().unwrap(),
485
            LayoutPaddingParseError::TooManyValues
486
        ));
487
1
        assert!(matches!(
488
1
            parse_layout_padding("1px oops 3px").err().unwrap(),
489
            LayoutPaddingParseError::PixelValueParseError(_)
490
        ));
491
1
    }
492

            
493
    #[test]
494
1
    fn test_parse_layout_margin_shorthand() {
495
        // 1 value with auto
496
1
        let result = parse_layout_margin("auto").unwrap();
497
1
        assert_eq!(result.top, PixelValueWithAuto::Auto);
498
1
        assert_eq!(result.right, PixelValueWithAuto::Auto);
499
1
        assert_eq!(result.bottom, PixelValueWithAuto::Auto);
500
1
        assert_eq!(result.left, PixelValueWithAuto::Auto);
501

            
502
        // 2 values
503
1
        let result = parse_layout_margin("10px auto").unwrap();
504
1
        assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
505
1
        assert_eq!(result.right, PixelValueWithAuto::Auto);
506
1
        assert_eq!(
507
            result.bottom,
508
1
            PixelValueWithAuto::Exact(PixelValue::px(10.0))
509
        );
510
1
        assert_eq!(result.left, PixelValueWithAuto::Auto);
511
1
    }
512

            
513
    #[test]
514
1
    fn test_parse_layout_margin_errors() {
515
1
        assert!(matches!(
516
1
            parse_layout_margin("").err().unwrap(),
517
            LayoutMarginParseError::TooFewValues
518
        ));
519
1
        assert!(matches!(
520
1
            parse_layout_margin("1px 2px 3px 4px 5px").err().unwrap(),
521
            LayoutMarginParseError::TooManyValues
522
        ));
523
1
        assert!(matches!(
524
1
            parse_layout_margin("1px invalid").err().unwrap(),
525
            LayoutMarginParseError::PixelValueParseError(_)
526
        ));
527
1
    }
528

            
529
    #[test]
530
1
    fn test_parse_longhand_spacing() {
531
1
        assert_eq!(
532
1
            parse_layout_padding_left("2em").unwrap(),
533
1
            LayoutPaddingLeft {
534
1
                inner: PixelValue::em(2.0)
535
1
            }
536
        );
537
1
        assert!(parse_layout_margin_top("auto").is_err()); // Longhands don't parse "auto"
538
1
        assert_eq!(
539
1
            parse_layout_column_gap("20px").unwrap(),
540
1
            LayoutColumnGap {
541
1
                inner: PixelValue::px(20.0)
542
1
            }
543
        );
544
1
    }
545
}