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<'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#[cfg(feature = "parser")]
192#[derive(Clone, PartialEq)]
193pub enum LayoutPaddingParseError<'a> {
194 PixelValueParseError(CssPixelValueParseError<'a>),
195 TooManyValues,
196 TooFewValues,
197}
198
199#[cfg(feature = "parser")]
201#[derive(Debug, Clone, PartialEq)]
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<'a>(
224 input: &'a str,
225) -> Result<LayoutPadding, LayoutPaddingParseError<'a>> {
226 let values: Vec<_> = input.split_whitespace().collect();
227
228 let parsed_values: Vec<PixelValueWithAuto> = values
229 .iter()
230 .map(|s| parse_pixel_value_with_auto(s))
231 .collect::<Result<_, _>>()?;
232
233 match parsed_values.len() {
234 1 => {
235 let all = parsed_values[0];
237 Ok(LayoutPadding {
238 top: all,
239 right: all,
240 bottom: all,
241 left: all,
242 })
243 }
244 2 => {
245 let vertical = parsed_values[0];
247 let horizontal = parsed_values[1];
248 Ok(LayoutPadding {
249 top: vertical,
250 right: horizontal,
251 bottom: vertical,
252 left: horizontal,
253 })
254 }
255 3 => {
256 let top = parsed_values[0];
258 let horizontal = parsed_values[1];
259 let bottom = parsed_values[2];
260 Ok(LayoutPadding {
261 top,
262 right: horizontal,
263 bottom,
264 left: horizontal,
265 })
266 }
267 4 => {
268 Ok(LayoutPadding {
270 top: parsed_values[0],
271 right: parsed_values[1],
272 bottom: parsed_values[2],
273 left: parsed_values[3],
274 })
275 }
276 0 => Err(LayoutPaddingParseError::TooFewValues),
277 _ => Err(LayoutPaddingParseError::TooManyValues),
278 }
279}
280
281#[cfg(feature = "parser")]
285#[derive(Clone, PartialEq)]
286pub enum LayoutMarginParseError<'a> {
287 PixelValueParseError(CssPixelValueParseError<'a>),
288 TooManyValues,
289 TooFewValues,
290}
291
292#[cfg(feature = "parser")]
294#[derive(Debug, Clone, PartialEq)]
295#[repr(C, u8)]
296pub enum LayoutMarginParseErrorOwned {
297 PixelValueParseError(CssPixelValueParseErrorOwned),
298 TooManyValues,
299 TooFewValues,
300}
301
302#[cfg(feature = "parser")]
303impl_spacing_parse_error!(LayoutMarginParseError, LayoutMarginParseErrorOwned, "margin");
304
305#[cfg(feature = "parser")]
307#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
308pub struct LayoutMargin {
309 pub top: PixelValueWithAuto,
310 pub bottom: PixelValueWithAuto,
311 pub left: PixelValueWithAuto,
312 pub right: PixelValueWithAuto,
313}
314
315#[cfg(feature = "parser")]
316pub fn parse_layout_margin<'a>(input: &'a str) -> Result<LayoutMargin, LayoutMarginParseError<'a>> {
317 match parse_layout_padding(input) {
320 Ok(padding) => Ok(LayoutMargin {
321 top: padding.top,
322 left: padding.left,
323 right: padding.right,
324 bottom: padding.bottom,
325 }),
326 Err(e) => match e {
327 LayoutPaddingParseError::PixelValueParseError(err) => {
328 Err(LayoutMarginParseError::PixelValueParseError(err))
329 }
330 LayoutPaddingParseError::TooManyValues => Err(LayoutMarginParseError::TooManyValues),
331 LayoutPaddingParseError::TooFewValues => Err(LayoutMarginParseError::TooFewValues),
332 },
333 }
334}
335
336macro_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 #[doc = $return_str]
344 #[doc = $import_str]
350 #[doc = $test_str]
351 pub fn $fn<'a>(input: &'a str) -> Result<$return, CssPixelValueParseError<'a>> {
353 crate::props::basic::parse_pixel_value(input).map(|e| $return { inner: e })
354 }
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")]
392typed_pixel_value_parser!(parse_layout_padding_top, LayoutPaddingTop);
393#[cfg(feature = "parser")]
394typed_pixel_value_parser!(parse_layout_padding_right, LayoutPaddingRight);
395#[cfg(feature = "parser")]
396typed_pixel_value_parser!(parse_layout_padding_bottom, LayoutPaddingBottom);
397#[cfg(feature = "parser")]
398typed_pixel_value_parser!(parse_layout_padding_left, LayoutPaddingLeft);
399#[cfg(feature = "parser")]
400typed_pixel_value_parser!(parse_layout_padding_inline_start, LayoutPaddingInlineStart);
401#[cfg(feature = "parser")]
402typed_pixel_value_parser!(parse_layout_padding_inline_end, LayoutPaddingInlineEnd);
403
404#[cfg(feature = "parser")]
405typed_pixel_value_parser!(parse_layout_margin_top, LayoutMarginTop);
406#[cfg(feature = "parser")]
407typed_pixel_value_parser!(parse_layout_margin_right, LayoutMarginRight);
408#[cfg(feature = "parser")]
409typed_pixel_value_parser!(parse_layout_margin_bottom, LayoutMarginBottom);
410#[cfg(feature = "parser")]
411typed_pixel_value_parser!(parse_layout_margin_left, LayoutMarginLeft);
412
413#[cfg(feature = "parser")]
414typed_pixel_value_parser!(parse_layout_column_gap, LayoutColumnGap);
415#[cfg(feature = "parser")]
416typed_pixel_value_parser!(parse_layout_row_gap, LayoutRowGap);
417
418#[cfg(all(test, feature = "parser"))]
419mod tests {
420 use super::*;
421 use crate::props::basic::pixel::{PixelValue, PixelValueWithAuto};
422
423 #[test]
424 fn test_parse_layout_padding_shorthand() {
425 let result = parse_layout_padding("10px").unwrap();
427 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
428 assert_eq!(
429 result.right,
430 PixelValueWithAuto::Exact(PixelValue::px(10.0))
431 );
432 assert_eq!(
433 result.bottom,
434 PixelValueWithAuto::Exact(PixelValue::px(10.0))
435 );
436 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
437
438 let result = parse_layout_padding("5% 2em").unwrap();
440 assert_eq!(
441 result.top,
442 PixelValueWithAuto::Exact(PixelValue::percent(5.0))
443 );
444 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
445 assert_eq!(
446 result.bottom,
447 PixelValueWithAuto::Exact(PixelValue::percent(5.0))
448 );
449 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::em(2.0)));
450
451 let result = parse_layout_padding("1px 2px 3px").unwrap();
453 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
454 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
455 assert_eq!(
456 result.bottom,
457 PixelValueWithAuto::Exact(PixelValue::px(3.0))
458 );
459 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
460
461 let result = parse_layout_padding("1px 2px 3px 4px").unwrap();
463 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
464 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
465 assert_eq!(
466 result.bottom,
467 PixelValueWithAuto::Exact(PixelValue::px(3.0))
468 );
469 assert_eq!(result.left, PixelValueWithAuto::Exact(PixelValue::px(4.0)));
470
471 let result = parse_layout_padding(" 1px 2px ").unwrap();
473 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(1.0)));
474 assert_eq!(result.right, PixelValueWithAuto::Exact(PixelValue::px(2.0)));
475 }
476
477 #[test]
478 fn test_parse_layout_padding_errors() {
479 assert!(matches!(
480 parse_layout_padding("").err().unwrap(),
481 LayoutPaddingParseError::TooFewValues
482 ));
483 assert!(matches!(
484 parse_layout_padding("1px 2px 3px 4px 5px").err().unwrap(),
485 LayoutPaddingParseError::TooManyValues
486 ));
487 assert!(matches!(
488 parse_layout_padding("1px oops 3px").err().unwrap(),
489 LayoutPaddingParseError::PixelValueParseError(_)
490 ));
491 }
492
493 #[test]
494 fn test_parse_layout_margin_shorthand() {
495 let result = parse_layout_margin("auto").unwrap();
497 assert_eq!(result.top, PixelValueWithAuto::Auto);
498 assert_eq!(result.right, PixelValueWithAuto::Auto);
499 assert_eq!(result.bottom, PixelValueWithAuto::Auto);
500 assert_eq!(result.left, PixelValueWithAuto::Auto);
501
502 let result = parse_layout_margin("10px auto").unwrap();
504 assert_eq!(result.top, PixelValueWithAuto::Exact(PixelValue::px(10.0)));
505 assert_eq!(result.right, PixelValueWithAuto::Auto);
506 assert_eq!(
507 result.bottom,
508 PixelValueWithAuto::Exact(PixelValue::px(10.0))
509 );
510 assert_eq!(result.left, PixelValueWithAuto::Auto);
511 }
512
513 #[test]
514 fn test_parse_layout_margin_errors() {
515 assert!(matches!(
516 parse_layout_margin("").err().unwrap(),
517 LayoutMarginParseError::TooFewValues
518 ));
519 assert!(matches!(
520 parse_layout_margin("1px 2px 3px 4px 5px").err().unwrap(),
521 LayoutMarginParseError::TooManyValues
522 ));
523 assert!(matches!(
524 parse_layout_margin("1px invalid").err().unwrap(),
525 LayoutMarginParseError::PixelValueParseError(_)
526 ));
527 }
528
529 #[test]
530 fn test_parse_longhand_spacing() {
531 assert_eq!(
532 parse_layout_padding_left("2em").unwrap(),
533 LayoutPaddingLeft {
534 inner: PixelValue::em(2.0)
535 }
536 );
537 assert!(parse_layout_margin_top("auto").is_err()); assert_eq!(
539 parse_layout_column_gap("20px").unwrap(),
540 LayoutColumnGap {
541 inner: PixelValue::px(20.0)
542 }
543 );
544 }
545}