1use alloc::{
7 string::{String, ToString},
8 vec::Vec,
9};
10
11#[cfg(feature = "parser")]
12use crate::props::basic::pixel::{parse_pixel_value_with_auto, PixelValueWithAuto};
13use crate::{
14 css::PrintAsCssValue,
15 props::{
16 basic::pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
17 macros::PixelValueTaker,
18 },
19};
20
21macro_rules! impl_spacing_type_impls {
26 ($name:ident) => {
27 impl ::core::fmt::Debug for $name {
28 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
29 write!(f, "{}", self.inner)
30 }
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#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45#[repr(C)]
46pub struct LayoutPaddingTop {
47 pub inner: PixelValue,
48}
49impl_spacing_type_impls!(LayoutPaddingTop);
50
51#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[repr(C)]
54pub struct LayoutPaddingRight {
55 pub inner: PixelValue,
56}
57impl_spacing_type_impls!(LayoutPaddingRight);
58
59#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub struct LayoutPaddingBottom {
63 pub inner: PixelValue,
64}
65impl_spacing_type_impls!(LayoutPaddingBottom);
66
67#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69#[repr(C)]
70pub struct LayoutPaddingLeft {
71 pub inner: PixelValue,
72}
73impl_spacing_type_impls!(LayoutPaddingLeft);
74
75#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[repr(C)]
78pub struct LayoutPaddingInlineStart {
79 pub inner: PixelValue,
80}
81impl_spacing_type_impls!(LayoutPaddingInlineStart);
82
83#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[repr(C)]
86pub struct LayoutPaddingInlineEnd {
87 pub inner: PixelValue,
88}
89impl_spacing_type_impls!(LayoutPaddingInlineEnd);
90
91#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93#[repr(C)]
94pub struct LayoutMarginTop {
95 pub inner: PixelValue,
96}
97impl_spacing_type_impls!(LayoutMarginTop);
98
99#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101#[repr(C)]
102pub struct LayoutMarginRight {
103 pub inner: PixelValue,
104}
105impl_spacing_type_impls!(LayoutMarginRight);
106
107#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
109#[repr(C)]
110pub struct LayoutMarginBottom {
111 pub inner: PixelValue,
112}
113impl_spacing_type_impls!(LayoutMarginBottom);
114
115#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117#[repr(C)]
118pub struct LayoutMarginLeft {
119 pub inner: PixelValue,
120}
121impl_spacing_type_impls!(LayoutMarginLeft);
122
123#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[repr(C)]
126pub struct LayoutColumnGap {
127 pub inner: PixelValue,
128}
129impl_spacing_type_impls!(LayoutColumnGap);
130
131#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133#[repr(C)]
134pub struct LayoutRowGap {
135 pub inner: PixelValue,
136}
137impl_spacing_type_impls!(LayoutRowGap);
138
139#[cfg(feature = "parser")]
142macro_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 $borrowed<'_> {
162 #[must_use] 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 #[must_use] pub fn to_shared(&self) -> $borrowed<'_> {
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#[cfg(feature = "parser")]
192#[derive(Clone, PartialEq, Eq)]
193pub enum LayoutPaddingParseError<'a> {
194 PixelValueParseError(CssPixelValueParseError<'a>),
195 TooManyValues,
196 TooFewValues,
197}
198#[allow(variant_size_differences)] #[cfg(feature = "parser")]
201#[derive(Debug, Clone, PartialEq, Eq)]
202#[repr(C, u8)]
203pub enum LayoutPaddingParseErrorOwned {
204 PixelValueParseError(CssPixelValueParseErrorOwned),
205 TooManyValues,
206 TooFewValues,
207}
208
209#[cfg(feature = "parser")]
210impl_spacing_parse_error!(LayoutPaddingParseError, LayoutPaddingParseErrorOwned, "padding");
211
212#[cfg(feature = "parser")]
214#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
215pub struct LayoutPadding {
216 pub top: PixelValueWithAuto,
217 pub bottom: PixelValueWithAuto,
218 pub left: PixelValueWithAuto,
219 pub right: PixelValueWithAuto,
220}
221
222#[cfg(feature = "parser")]
223pub fn parse_layout_padding(
227 input: &str,
228) -> Result<LayoutPadding, LayoutPaddingParseError<'_>> {
229 let values: Vec<_> = input.split_whitespace().collect();
230
231 let parsed_values: Vec<PixelValueWithAuto> = values
232 .iter()
233 .map(|s| parse_pixel_value_with_auto(s))
234 .collect::<Result<_, _>>()?;
235
236 match parsed_values.len() {
237 1 => {
238 let all = parsed_values[0];
240 Ok(LayoutPadding {
241 top: all,
242 right: all,
243 bottom: all,
244 left: all,
245 })
246 }
247 2 => {
248 let vertical = parsed_values[0];
250 let horizontal = parsed_values[1];
251 Ok(LayoutPadding {
252 top: vertical,
253 right: horizontal,
254 bottom: vertical,
255 left: horizontal,
256 })
257 }
258 3 => {
259 let top = parsed_values[0];
261 let horizontal = parsed_values[1];
262 let bottom = parsed_values[2];
263 Ok(LayoutPadding {
264 top,
265 right: horizontal,
266 bottom,
267 left: horizontal,
268 })
269 }
270 4 => {
271 Ok(LayoutPadding {
273 top: parsed_values[0],
274 right: parsed_values[1],
275 bottom: parsed_values[2],
276 left: parsed_values[3],
277 })
278 }
279 0 => Err(LayoutPaddingParseError::TooFewValues),
280 _ => Err(LayoutPaddingParseError::TooManyValues),
281 }
282}
283
284#[cfg(feature = "parser")]
288#[derive(Clone, PartialEq, Eq)]
289pub enum LayoutMarginParseError<'a> {
290 PixelValueParseError(CssPixelValueParseError<'a>),
291 TooManyValues,
292 TooFewValues,
293}
294#[allow(variant_size_differences)] #[cfg(feature = "parser")]
297#[derive(Debug, Clone, PartialEq, Eq)]
298#[repr(C, u8)]
299pub enum LayoutMarginParseErrorOwned {
300 PixelValueParseError(CssPixelValueParseErrorOwned),
301 TooManyValues,
302 TooFewValues,
303}
304
305#[cfg(feature = "parser")]
306impl_spacing_parse_error!(LayoutMarginParseError, LayoutMarginParseErrorOwned, "margin");
307
308#[cfg(feature = "parser")]
310#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
311pub struct LayoutMargin {
312 pub top: PixelValueWithAuto,
313 pub bottom: PixelValueWithAuto,
314 pub left: PixelValueWithAuto,
315 pub right: PixelValueWithAuto,
316}
317
318#[cfg(feature = "parser")]
319pub fn parse_layout_margin(input: &str) -> Result<LayoutMargin, LayoutMarginParseError<'_>> {
323 match parse_layout_padding(input) {
326 Ok(padding) => Ok(LayoutMargin {
327 top: padding.top,
328 left: padding.left,
329 right: padding.right,
330 bottom: padding.bottom,
331 }),
332 Err(e) => match e {
333 LayoutPaddingParseError::PixelValueParseError(err) => {
334 Err(LayoutMarginParseError::PixelValueParseError(err))
335 }
336 LayoutPaddingParseError::TooManyValues => Err(LayoutMarginParseError::TooManyValues),
337 LayoutPaddingParseError::TooFewValues => Err(LayoutMarginParseError::TooFewValues),
338 },
339 }
340}
341
342macro_rules! typed_pixel_value_parser {
345 (
346 $fn:ident, $fn_str:expr, $return:ident, $return_str:expr, $import_str:expr, $test_str:expr
347 ) => {
348 #[doc = $return_str]
350 #[doc = $import_str]
356 #[doc = $test_str]
357 pub fn $fn(input: &str) -> Result<$return, CssPixelValueParseError<'_>> {
362 crate::props::basic::parse_pixel_value(input).map(|e| $return { inner: e })
363 }
364
365 impl crate::props::formatter::FormatAsCssValue for $return {
366 fn format_as_css_value(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 self.inner.format_as_css_value(f)
368 }
369 }
370 };
371 ($fn:ident, $return:ident) => {
372 typed_pixel_value_parser!(
373 $fn,
374 stringify!($fn),
375 $return,
376 stringify!($return),
377 concat!(
378 "# extern crate azul_css;",
379 "\r\n",
380 "# use azul_css::props::layout::spacing::",
381 stringify!($fn),
382 ";",
383 "\r\n",
384 "# use azul_css::props::basic::pixel::PixelValue;\r\n",
385 "# use azul_css::props::layout::spacing::",
386 stringify!($return),
387 ";\r\n"
388 ),
389 concat!(
390 "assert_eq!(",
391 stringify!($fn),
392 "(\"5px\"), Ok(",
393 stringify!($return),
394 " { inner: PixelValue::px(5.0) }));"
395 )
396 );
397 };
398}
399
400#[cfg(feature = "parser")]
401typed_pixel_value_parser!(parse_layout_padding_top, LayoutPaddingTop);
402#[cfg(feature = "parser")]
403typed_pixel_value_parser!(parse_layout_padding_right, LayoutPaddingRight);
404#[cfg(feature = "parser")]
405typed_pixel_value_parser!(parse_layout_padding_bottom, LayoutPaddingBottom);
406#[cfg(feature = "parser")]
407typed_pixel_value_parser!(parse_layout_padding_left, LayoutPaddingLeft);
408#[cfg(feature = "parser")]
409typed_pixel_value_parser!(parse_layout_padding_inline_start, LayoutPaddingInlineStart);
410#[cfg(feature = "parser")]
411typed_pixel_value_parser!(parse_layout_padding_inline_end, LayoutPaddingInlineEnd);
412
413#[cfg(feature = "parser")]
414typed_pixel_value_parser!(parse_layout_margin_top, LayoutMarginTop);
415#[cfg(feature = "parser")]
416typed_pixel_value_parser!(parse_layout_margin_right, LayoutMarginRight);
417#[cfg(feature = "parser")]
418typed_pixel_value_parser!(parse_layout_margin_bottom, LayoutMarginBottom);
419#[cfg(feature = "parser")]
420typed_pixel_value_parser!(parse_layout_margin_left, LayoutMarginLeft);
421
422#[cfg(feature = "parser")]
423typed_pixel_value_parser!(parse_layout_column_gap, LayoutColumnGap);
424#[cfg(feature = "parser")]
425typed_pixel_value_parser!(parse_layout_row_gap, LayoutRowGap);
426
427#[cfg(all(test, feature = "parser"))]
428mod tests {
429 use super::*;
430 use crate::props::basic::pixel::{PixelValue, PixelValueWithAuto};
431
432 #[test]
433 fn test_parse_layout_padding_shorthand() {
434 let result = parse_layout_padding("10px").unwrap();
436 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
437 assert_eq!(
438 result.right,
439 PixelValueWithAuto::Exact(PixelValue::px(10.0))
440 );
441 assert_eq!(
442 result.bottom,
443 PixelValueWithAuto::Exact(PixelValue::px(10.0))
444 );
445 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
446
447 let result = parse_layout_padding("5% 2em").unwrap();
449 assert_eq!(
450 result.top,
451 PixelValueWithAuto::Exact(PixelValue::percent(5.0))
452 );
453 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
454 assert_eq!(
455 result.bottom,
456 PixelValueWithAuto::Exact(PixelValue::percent(5.0))
457 );
458 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
459
460 let result = parse_layout_padding("1px 2px 3px").unwrap();
462 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
463 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
464 assert_eq!(
465 result.bottom,
466 PixelValueWithAuto::Exact(PixelValue::px(3.0))
467 );
468 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
469
470 let result = parse_layout_padding("1px 2px 3px 4px").unwrap();
472 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
473 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
474 assert_eq!(
475 result.bottom,
476 PixelValueWithAuto::Exact(PixelValue::px(3.0))
477 );
478 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(4.0)));
479
480 let result = parse_layout_padding(" 1px 2px ").unwrap();
482 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
483 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
484 }
485
486 #[test]
487 fn test_parse_layout_padding_errors() {
488 assert!(matches!(
489 parse_layout_padding("").err().unwrap(),
490 LayoutPaddingParseError::TooFewValues
491 ));
492 assert!(matches!(
493 parse_layout_padding("1px 2px 3px 4px 5px").err().unwrap(),
494 LayoutPaddingParseError::TooManyValues
495 ));
496 assert!(matches!(
497 parse_layout_padding("1px oops 3px").err().unwrap(),
498 LayoutPaddingParseError::PixelValueParseError(_)
499 ));
500 }
501
502 #[test]
503 fn test_parse_layout_margin_shorthand() {
504 let result = parse_layout_margin("auto").unwrap();
506 assert_eq!(result.top, PixelValueWithAuto::Auto);
507 assert_eq!(result.right, PixelValueWithAuto::Auto);
508 assert_eq!(result.bottom, PixelValueWithAuto::Auto);
509 assert_eq!(result.left, PixelValueWithAuto::Auto);
510
511 let result = parse_layout_margin("10px auto").unwrap();
513 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
514 assert_eq!(result.right, PixelValueWithAuto::Auto);
515 assert_eq!(
516 result.bottom,
517 PixelValueWithAuto::Exact(PixelValue::px(10.0))
518 );
519 assert_eq!(result.left, PixelValueWithAuto::Auto);
520 }
521
522 #[test]
523 fn test_parse_layout_margin_errors() {
524 assert!(matches!(
525 parse_layout_margin("").err().unwrap(),
526 LayoutMarginParseError::TooFewValues
527 ));
528 assert!(matches!(
529 parse_layout_margin("1px 2px 3px 4px 5px").err().unwrap(),
530 LayoutMarginParseError::TooManyValues
531 ));
532 assert!(matches!(
533 parse_layout_margin("1px invalid").err().unwrap(),
534 LayoutMarginParseError::PixelValueParseError(_)
535 ));
536 }
537
538 #[test]
539 fn test_parse_longhand_spacing() {
540 assert_eq!(
541 parse_layout_padding_left("2em").unwrap(),
542 LayoutPaddingLeft {
543 inner: PixelValue::em(2.0)
544 }
545 );
546 assert!(parse_layout_margin_top("auto").is_err()); assert_eq!(
548 parse_layout_column_gap("20px").unwrap(),
549 LayoutColumnGap {
550 inner: PixelValue::px(20.0)
551 }
552 );
553 }
554}