Skip to main content

azul_css/props/layout/
flex.rs

1//! CSS properties for flexbox layout.
2
3use alloc::string::{String, ToString};
4use core::num::ParseFloatError;
5use crate::corety::AzString;
6
7use crate::{
8    codegen::format::FormatAsRustCode,
9    props::{
10        basic::{
11            error::ParseFloatErrorWithInput,
12            length::{parse_float_value, FloatValue},
13        },
14        formatter::PrintAsCssValue,
15    },
16};
17
18// --- flex-grow ---
19
20/// Represents a `flex-grow` attribute, which dictates what proportion of the
21/// remaining space in the flex container should be assigned to the item.
22/// Default: 0
23#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(C)]
25pub struct LayoutFlexGrow {
26    pub inner: FloatValue,
27}
28
29impl core::fmt::Debug for LayoutFlexGrow {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        write!(f, "{}", self.inner.get())
32    }
33}
34
35impl Default for LayoutFlexGrow {
36    fn default() -> Self {
37        Self {
38            inner: FloatValue::const_new(0),
39        }
40    }
41}
42
43impl PrintAsCssValue for LayoutFlexGrow {
44    fn print_as_css_value(&self) -> String {
45        format!("{}", self.inner)
46    }
47}
48
49impl LayoutFlexGrow {
50    #[must_use] pub fn new(value: isize) -> Self {
51        Self {
52            inner: FloatValue::new(crate::cast::isize_to_f32(value)),
53        }
54    }
55
56    #[must_use] pub const fn const_new(value: isize) -> Self {
57        Self {
58            inner: FloatValue::const_new(value),
59        }
60    }
61
62    #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
63        Self {
64            inner: self.inner.interpolate(&other.inner, t),
65        }
66    }
67}
68
69#[cfg(feature = "parser")]
70#[derive(Clone, PartialEq, Eq)]
71pub enum FlexGrowParseError<'a> {
72    ParseFloat(ParseFloatError, &'a str),
73    NegativeValue(&'a str),
74}
75
76#[cfg(feature = "parser")]
77impl_debug_as_display!(FlexGrowParseError<'a>);
78#[cfg(feature = "parser")]
79impl_display! { FlexGrowParseError<'a>, {
80    ParseFloat(e, s) => format!("Invalid flex-grow value: \"{}\". Reason: {}", s, e),
81    NegativeValue(s) => format!("Invalid flex-grow value: \"{}\". Flex-grow cannot be negative", s),
82}}
83
84#[cfg(feature = "parser")]
85#[derive(Debug, Clone, PartialEq, Eq)]
86#[repr(C, u8)]
87pub enum FlexGrowParseErrorOwned {
88    ParseFloat(ParseFloatErrorWithInput),
89    NegativeValue(AzString),
90}
91
92#[cfg(feature = "parser")]
93impl FlexGrowParseError<'_> {
94    #[must_use] pub fn to_contained(&self) -> FlexGrowParseErrorOwned {
95        match self {
96            FlexGrowParseError::ParseFloat(e, s) => {
97                FlexGrowParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
98            }
99            FlexGrowParseError::NegativeValue(s) => {
100                FlexGrowParseErrorOwned::NegativeValue((*s).to_string().into())
101            }
102        }
103    }
104}
105
106#[cfg(feature = "parser")]
107impl FlexGrowParseErrorOwned {
108    #[must_use] pub fn to_shared(&self) -> FlexGrowParseError<'_> {
109        match self {
110            Self::ParseFloat(e) => {
111                FlexGrowParseError::ParseFloat(e.error.to_std(), e.input.as_str())
112            }
113            Self::NegativeValue(s) => {
114                FlexGrowParseError::NegativeValue(s.as_str())
115            }
116        }
117    }
118}
119
120#[cfg(feature = "parser")]
121/// # Errors
122///
123/// Returns an error if `input` is not a valid CSS `flex-grow` value.
124pub fn parse_layout_flex_grow(
125    input: &str,
126) -> Result<LayoutFlexGrow, FlexGrowParseError<'_>> {
127    match parse_float_value(input) {
128        Ok(o) => {
129            if o.get() < 0.0 {
130                Err(FlexGrowParseError::NegativeValue(input))
131            } else {
132                Ok(LayoutFlexGrow { inner: o })
133            }
134        }
135        Err(e) => Err(FlexGrowParseError::ParseFloat(e, input)),
136    }
137}
138
139// --- flex-shrink ---
140
141/// Represents a `flex-shrink` attribute, which dictates what proportion of
142/// the negative space in the flex container should be removed from the item.
143/// Default: 1
144#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
145#[repr(C)]
146pub struct LayoutFlexShrink {
147    pub inner: FloatValue,
148}
149
150impl core::fmt::Debug for LayoutFlexShrink {
151    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152        write!(f, "{}", self.inner.get())
153    }
154}
155
156impl Default for LayoutFlexShrink {
157    fn default() -> Self {
158        Self {
159            inner: FloatValue::const_new(1),
160        }
161    }
162}
163
164impl PrintAsCssValue for LayoutFlexShrink {
165    fn print_as_css_value(&self) -> String {
166        format!("{}", self.inner)
167    }
168}
169
170impl LayoutFlexShrink {
171    #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
172        Self {
173            inner: self.inner.interpolate(&other.inner, t),
174        }
175    }
176}
177
178#[cfg(feature = "parser")]
179#[derive(Clone, PartialEq, Eq)]
180pub enum FlexShrinkParseError<'a> {
181    ParseFloat(ParseFloatError, &'a str),
182    NegativeValue(&'a str),
183}
184
185#[cfg(feature = "parser")]
186impl_debug_as_display!(FlexShrinkParseError<'a>);
187#[cfg(feature = "parser")]
188impl_display! { FlexShrinkParseError<'a>, {
189    ParseFloat(e, s) => format!("Invalid flex-shrink value: \"{}\". Reason: {}", s, e),
190    NegativeValue(s) => format!("Invalid flex-shrink value: \"{}\". Flex-shrink cannot be negative", s),
191}}
192
193#[cfg(feature = "parser")]
194#[derive(Debug, Clone, PartialEq, Eq)]
195#[repr(C, u8)]
196pub enum FlexShrinkParseErrorOwned {
197    ParseFloat(ParseFloatErrorWithInput),
198    NegativeValue(AzString),
199}
200
201#[cfg(feature = "parser")]
202impl FlexShrinkParseError<'_> {
203    #[must_use] pub fn to_contained(&self) -> FlexShrinkParseErrorOwned {
204        match self {
205            FlexShrinkParseError::ParseFloat(e, s) => {
206                FlexShrinkParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
207            }
208            FlexShrinkParseError::NegativeValue(s) => {
209                FlexShrinkParseErrorOwned::NegativeValue((*s).to_string().into())
210            }
211        }
212    }
213}
214
215#[cfg(feature = "parser")]
216impl FlexShrinkParseErrorOwned {
217    #[must_use] pub fn to_shared(&self) -> FlexShrinkParseError<'_> {
218        match self {
219            Self::ParseFloat(e) => {
220                FlexShrinkParseError::ParseFloat(e.error.to_std(), e.input.as_str())
221            }
222            Self::NegativeValue(s) => {
223                FlexShrinkParseError::NegativeValue(s.as_str())
224            }
225        }
226    }
227}
228
229#[cfg(feature = "parser")]
230/// # Errors
231///
232/// Returns an error if `input` is not a valid CSS `flex-shrink` value.
233pub fn parse_layout_flex_shrink(
234    input: &str,
235) -> Result<LayoutFlexShrink, FlexShrinkParseError<'_>> {
236    match parse_float_value(input) {
237        Ok(o) => {
238            if o.get() < 0.0 {
239                Err(FlexShrinkParseError::NegativeValue(input))
240            } else {
241                Ok(LayoutFlexShrink { inner: o })
242            }
243        }
244        Err(e) => Err(FlexShrinkParseError::ParseFloat(e, input)),
245    }
246}
247
248// --- flex-direction ---
249
250/// Represents a `flex-direction` attribute, which establishes the main-axis,
251/// thus defining the direction flex items are placed in the flex container.
252/// Default: `Row`
253#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[repr(C)]
255#[derive(Default)]
256pub enum LayoutFlexDirection {
257    #[default]
258    Row,
259    RowReverse,
260    Column,
261    ColumnReverse,
262}
263
264
265/// Represents the main or cross axis of a flex container.
266#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
267#[repr(C)]
268pub enum LayoutAxis {
269    Horizontal,
270    Vertical,
271}
272
273impl LayoutFlexDirection {
274    #[must_use] pub const fn get_axis(&self) -> LayoutAxis {
275        match self {
276            Self::Row | Self::RowReverse => LayoutAxis::Horizontal,
277            Self::Column | Self::ColumnReverse => LayoutAxis::Vertical,
278        }
279    }
280
281    #[must_use] pub const fn is_reverse(&self) -> bool {
282        matches!(self, Self::RowReverse | Self::ColumnReverse)
283    }
284}
285
286impl PrintAsCssValue for LayoutFlexDirection {
287    fn print_as_css_value(&self) -> String {
288        String::from(match self {
289            Self::Row => "row",
290            Self::RowReverse => "row-reverse",
291            Self::Column => "column",
292            Self::ColumnReverse => "column-reverse",
293        })
294    }
295}
296
297#[cfg(feature = "parser")]
298#[derive(Clone, PartialEq, Eq)]
299pub enum FlexDirectionParseError<'a> {
300    InvalidValue(&'a str),
301}
302
303#[cfg(feature = "parser")]
304impl_debug_as_display!(FlexDirectionParseError<'a>);
305#[cfg(feature = "parser")]
306impl_display! { FlexDirectionParseError<'a>, {
307    InvalidValue(s) => format!("Invalid flex-direction value: \"{}\"", s),
308}}
309
310#[cfg(feature = "parser")]
311#[derive(Debug, Clone, PartialEq, Eq)]
312#[repr(C, u8)]
313pub enum FlexDirectionParseErrorOwned {
314    InvalidValue(AzString),
315}
316
317#[cfg(feature = "parser")]
318impl FlexDirectionParseError<'_> {
319    #[must_use] pub fn to_contained(&self) -> FlexDirectionParseErrorOwned {
320        match self {
321            Self::InvalidValue(s) => FlexDirectionParseErrorOwned::InvalidValue((*s).to_string().into()),
322        }
323    }
324}
325
326#[cfg(feature = "parser")]
327impl FlexDirectionParseErrorOwned {
328    #[must_use] pub fn to_shared(&self) -> FlexDirectionParseError<'_> {
329        match self {
330            Self::InvalidValue(s) => FlexDirectionParseError::InvalidValue(s.as_str()),
331        }
332    }
333}
334
335#[cfg(feature = "parser")]
336/// # Errors
337///
338/// Returns an error if `input` is not a valid CSS `flex-direction` value.
339pub fn parse_layout_flex_direction(
340    input: &str,
341) -> Result<LayoutFlexDirection, FlexDirectionParseError<'_>> {
342    match input.trim() {
343        "row" => Ok(LayoutFlexDirection::Row),
344        "row-reverse" => Ok(LayoutFlexDirection::RowReverse),
345        "column" => Ok(LayoutFlexDirection::Column),
346        "column-reverse" => Ok(LayoutFlexDirection::ColumnReverse),
347        _ => Err(FlexDirectionParseError::InvalidValue(input)),
348    }
349}
350
351// --- flex-wrap ---
352
353/// Represents a `flex-wrap` attribute, which determines whether flex items
354/// are forced onto one line or can wrap onto multiple lines.
355/// Default: `NoWrap`
356#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357#[repr(C)]
358#[derive(Default)]
359pub enum LayoutFlexWrap {
360    Wrap,
361    #[default]
362    NoWrap,
363    WrapReverse,
364}
365
366
367impl PrintAsCssValue for LayoutFlexWrap {
368    fn print_as_css_value(&self) -> String {
369        String::from(match self {
370            Self::Wrap => "wrap",
371            Self::NoWrap => "nowrap",
372            Self::WrapReverse => "wrap-reverse",
373        })
374    }
375}
376
377#[cfg(feature = "parser")]
378#[derive(Clone, PartialEq, Eq)]
379pub enum FlexWrapParseError<'a> {
380    InvalidValue(&'a str),
381}
382
383#[cfg(feature = "parser")]
384impl_debug_as_display!(FlexWrapParseError<'a>);
385#[cfg(feature = "parser")]
386impl_display! { FlexWrapParseError<'a>, {
387    InvalidValue(s) => format!("Invalid flex-wrap value: \"{}\"", s),
388}}
389
390#[cfg(feature = "parser")]
391#[derive(Debug, Clone, PartialEq, Eq)]
392#[repr(C, u8)]
393pub enum FlexWrapParseErrorOwned {
394    InvalidValue(AzString),
395}
396
397#[cfg(feature = "parser")]
398impl FlexWrapParseError<'_> {
399    #[must_use] pub fn to_contained(&self) -> FlexWrapParseErrorOwned {
400        match self {
401            Self::InvalidValue(s) => FlexWrapParseErrorOwned::InvalidValue((*s).to_string().into()),
402        }
403    }
404}
405
406#[cfg(feature = "parser")]
407impl FlexWrapParseErrorOwned {
408    #[must_use] pub fn to_shared(&self) -> FlexWrapParseError<'_> {
409        match self {
410            Self::InvalidValue(s) => FlexWrapParseError::InvalidValue(s.as_str()),
411        }
412    }
413}
414
415#[cfg(feature = "parser")]
416/// # Errors
417///
418/// Returns an error if `input` is not a valid CSS `flex-wrap` value.
419pub fn parse_layout_flex_wrap(
420    input: &str,
421) -> Result<LayoutFlexWrap, FlexWrapParseError<'_>> {
422    match input.trim() {
423        "wrap" => Ok(LayoutFlexWrap::Wrap),
424        "nowrap" => Ok(LayoutFlexWrap::NoWrap),
425        "wrap-reverse" => Ok(LayoutFlexWrap::WrapReverse),
426        _ => Err(FlexWrapParseError::InvalidValue(input)),
427    }
428}
429
430// --- justify-content ---
431
432/// Represents a `justify-content` attribute, which defines the alignment
433/// along the main axis.
434/// Default: `Start` (flex-start)
435#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436#[repr(C)]
437#[derive(Default)]
438pub enum LayoutJustifyContent {
439    FlexStart,
440    FlexEnd,
441    #[default]
442    Start,
443    End,
444    Center,
445    SpaceBetween,
446    SpaceAround,
447    SpaceEvenly,
448}
449
450
451impl PrintAsCssValue for LayoutJustifyContent {
452    fn print_as_css_value(&self) -> String {
453        String::from(match self {
454            Self::Start => "start",
455            Self::End => "end",
456            Self::FlexStart => "flex-start",
457            Self::FlexEnd => "flex-end",
458            Self::Center => "center",
459            Self::SpaceBetween => "space-between",
460            Self::SpaceAround => "space-around",
461            Self::SpaceEvenly => "space-evenly",
462        })
463    }
464}
465
466#[cfg(feature = "parser")]
467#[derive(Clone, PartialEq, Eq)]
468pub enum JustifyContentParseError<'a> {
469    InvalidValue(&'a str),
470}
471
472#[cfg(feature = "parser")]
473impl_debug_as_display!(JustifyContentParseError<'a>);
474#[cfg(feature = "parser")]
475impl_display! { JustifyContentParseError<'a>, {
476    InvalidValue(s) => format!("Invalid justify-content value: \"{}\"", s),
477}}
478
479#[cfg(feature = "parser")]
480#[derive(Debug, Clone, PartialEq, Eq)]
481#[repr(C, u8)]
482pub enum JustifyContentParseErrorOwned {
483    InvalidValue(AzString),
484}
485
486#[cfg(feature = "parser")]
487impl JustifyContentParseError<'_> {
488    #[must_use] pub fn to_contained(&self) -> JustifyContentParseErrorOwned {
489        match self {
490            Self::InvalidValue(s) => JustifyContentParseErrorOwned::InvalidValue((*s).to_string().into()),
491        }
492    }
493}
494
495#[cfg(feature = "parser")]
496impl JustifyContentParseErrorOwned {
497    #[must_use] pub fn to_shared(&self) -> JustifyContentParseError<'_> {
498        match self {
499            Self::InvalidValue(s) => JustifyContentParseError::InvalidValue(s.as_str()),
500        }
501    }
502}
503
504#[cfg(feature = "parser")]
505/// # Errors
506///
507/// Returns an error if `input` is not a valid CSS `justify-content` value.
508pub fn parse_layout_justify_content(
509    input: &str,
510) -> Result<LayoutJustifyContent, JustifyContentParseError<'_>> {
511    match input.trim() {
512        "flex-start" => Ok(LayoutJustifyContent::FlexStart),
513        "flex-end" => Ok(LayoutJustifyContent::FlexEnd),
514        "start" => Ok(LayoutJustifyContent::Start),
515        "end" => Ok(LayoutJustifyContent::End),
516        "center" => Ok(LayoutJustifyContent::Center),
517        "space-between" => Ok(LayoutJustifyContent::SpaceBetween),
518        "space-around" => Ok(LayoutJustifyContent::SpaceAround),
519        "space-evenly" => Ok(LayoutJustifyContent::SpaceEvenly),
520        _ => Err(JustifyContentParseError::InvalidValue(input)),
521    }
522}
523
524// --- align-items ---
525
526/// Represents an `align-items` attribute, which defines the default behavior for
527/// how flex items are laid out along the cross axis on the current line.
528/// Default: `Stretch`
529#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
530#[repr(C)]
531#[derive(Default)]
532pub enum LayoutAlignItems {
533    #[default]
534    Stretch,
535    Center,
536    Start,
537    End,
538    Baseline,
539}
540
541
542impl PrintAsCssValue for LayoutAlignItems {
543    fn print_as_css_value(&self) -> String {
544        String::from(match self {
545            Self::Stretch => "stretch",
546            Self::Center => "center",
547            Self::Start => "flex-start",
548            Self::End => "flex-end",
549            Self::Baseline => "baseline",
550        })
551    }
552}
553
554#[cfg(feature = "parser")]
555#[derive(Clone, PartialEq, Eq)]
556pub enum AlignItemsParseError<'a> {
557    InvalidValue(&'a str),
558}
559
560#[cfg(feature = "parser")]
561impl_debug_as_display!(AlignItemsParseError<'a>);
562#[cfg(feature = "parser")]
563impl_display! { AlignItemsParseError<'a>, {
564    InvalidValue(s) => format!("Invalid align-items value: \"{}\"", s),
565}}
566
567#[cfg(feature = "parser")]
568#[derive(Debug, Clone, PartialEq, Eq)]
569#[repr(C, u8)]
570pub enum AlignItemsParseErrorOwned {
571    InvalidValue(AzString),
572}
573
574#[cfg(feature = "parser")]
575impl AlignItemsParseError<'_> {
576    #[must_use] pub fn to_contained(&self) -> AlignItemsParseErrorOwned {
577        match self {
578            Self::InvalidValue(s) => AlignItemsParseErrorOwned::InvalidValue((*s).to_string().into()),
579        }
580    }
581}
582
583#[cfg(feature = "parser")]
584impl AlignItemsParseErrorOwned {
585    #[must_use] pub fn to_shared(&self) -> AlignItemsParseError<'_> {
586        match self {
587            Self::InvalidValue(s) => AlignItemsParseError::InvalidValue(s.as_str()),
588        }
589    }
590}
591
592#[cfg(feature = "parser")]
593/// # Errors
594///
595/// Returns an error if `input` is not a valid CSS `align-items` value.
596pub fn parse_layout_align_items(
597    input: &str,
598) -> Result<LayoutAlignItems, AlignItemsParseError<'_>> {
599    match input.trim() {
600        "stretch" => Ok(LayoutAlignItems::Stretch),
601        "center" => Ok(LayoutAlignItems::Center),
602        "start" | "flex-start" => Ok(LayoutAlignItems::Start),
603        "end" | "flex-end" => Ok(LayoutAlignItems::End),
604        "baseline" => Ok(LayoutAlignItems::Baseline),
605        _ => Err(AlignItemsParseError::InvalidValue(input)),
606    }
607}
608
609// --- align-content ---
610
611/// Represents an `align-content` attribute, which aligns a flex container's lines
612/// within it when there is extra space in the cross-axis.
613/// Default: `Stretch`
614#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
615#[repr(C)]
616#[derive(Default)]
617pub enum LayoutAlignContent {
618    #[default]
619    Stretch,
620    Center,
621    Start,
622    End,
623    SpaceBetween,
624    SpaceAround,
625}
626
627
628impl PrintAsCssValue for LayoutAlignContent {
629    fn print_as_css_value(&self) -> String {
630        String::from(match self {
631            Self::Stretch => "stretch",
632            Self::Center => "center",
633            Self::Start => "flex-start",
634            Self::End => "flex-end",
635            Self::SpaceBetween => "space-between",
636            Self::SpaceAround => "space-around",
637        })
638    }
639}
640
641#[cfg(feature = "parser")]
642#[derive(Clone, PartialEq, Eq)]
643pub enum AlignContentParseError<'a> {
644    InvalidValue(&'a str),
645}
646
647#[cfg(feature = "parser")]
648impl_debug_as_display!(AlignContentParseError<'a>);
649#[cfg(feature = "parser")]
650impl_display! { AlignContentParseError<'a>, {
651    InvalidValue(s) => format!("Invalid align-content value: \"{}\"", s),
652}}
653
654#[cfg(feature = "parser")]
655#[derive(Debug, Clone, PartialEq, Eq)]
656#[repr(C, u8)]
657pub enum AlignContentParseErrorOwned {
658    InvalidValue(AzString),
659}
660
661#[cfg(feature = "parser")]
662impl AlignContentParseError<'_> {
663    #[must_use] pub fn to_contained(&self) -> AlignContentParseErrorOwned {
664        match self {
665            Self::InvalidValue(s) => AlignContentParseErrorOwned::InvalidValue((*s).to_string().into()),
666        }
667    }
668}
669
670#[cfg(feature = "parser")]
671impl AlignContentParseErrorOwned {
672    #[must_use] pub fn to_shared(&self) -> AlignContentParseError<'_> {
673        match self {
674            Self::InvalidValue(s) => AlignContentParseError::InvalidValue(s.as_str()),
675        }
676    }
677}
678
679#[cfg(feature = "parser")]
680/// # Errors
681///
682/// Returns an error if `input` is not a valid CSS `align-content` value.
683pub fn parse_layout_align_content(
684    input: &str,
685) -> Result<LayoutAlignContent, AlignContentParseError<'_>> {
686    match input.trim() {
687        "stretch" => Ok(LayoutAlignContent::Stretch),
688        "center" => Ok(LayoutAlignContent::Center),
689        "start" | "flex-start" => Ok(LayoutAlignContent::Start),
690        "end" | "flex-end" => Ok(LayoutAlignContent::End),
691        "space-between" => Ok(LayoutAlignContent::SpaceBetween),
692        "space-around" => Ok(LayoutAlignContent::SpaceAround),
693        _ => Err(AlignContentParseError::InvalidValue(input)),
694    }
695}
696
697// --- align-self ---
698
699/// Represents an `align-self` attribute, which allows the default alignment
700/// (or the one specified by align-items) to be overridden for individual flex items.
701/// Default: `Auto`
702#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
703#[repr(C)]
704#[derive(Default)]
705pub enum LayoutAlignSelf {
706    #[default]
707    Auto,
708    Stretch,
709    Center,
710    Start,
711    End,
712    Baseline,
713}
714
715
716impl PrintAsCssValue for LayoutAlignSelf {
717    fn print_as_css_value(&self) -> String {
718        String::from(match self {
719            Self::Auto => "auto",
720            Self::Stretch => "stretch",
721            Self::Center => "center",
722            Self::Start => "flex-start",
723            Self::End => "flex-end",
724            Self::Baseline => "baseline",
725        })
726    }
727}
728
729impl FormatAsRustCode for LayoutAlignSelf {
730    fn format_as_rust_code(&self, _tabs: usize) -> String {
731        format!(
732            "LayoutAlignSelf::{}",
733            match self {
734                Self::Auto => "Auto",
735                Self::Stretch => "Stretch",
736                Self::Center => "Center",
737                Self::Start => "Start",
738                Self::End => "End",
739                Self::Baseline => "Baseline",
740            }
741        )
742    }
743}
744
745#[cfg(feature = "parser")]
746#[derive(Clone, PartialEq, Eq)]
747pub enum AlignSelfParseError<'a> {
748    InvalidValue(&'a str),
749}
750
751#[cfg(feature = "parser")]
752impl_debug_as_display!(AlignSelfParseError<'a>);
753#[cfg(feature = "parser")]
754impl_display! { AlignSelfParseError<'a>, {
755    InvalidValue(s) => format!("Invalid align-self value: \"{}\"", s),
756}}
757
758#[cfg(feature = "parser")]
759#[derive(Debug, Clone, PartialEq, Eq)]
760#[repr(C, u8)]
761pub enum AlignSelfParseErrorOwned {
762    InvalidValue(AzString),
763}
764
765#[cfg(feature = "parser")]
766impl AlignSelfParseError<'_> {
767    #[must_use] pub fn to_contained(&self) -> AlignSelfParseErrorOwned {
768        match self {
769            Self::InvalidValue(s) => AlignSelfParseErrorOwned::InvalidValue((*s).to_string().into()),
770        }
771    }
772}
773
774#[cfg(feature = "parser")]
775impl AlignSelfParseErrorOwned {
776    #[must_use] pub fn to_shared(&self) -> AlignSelfParseError<'_> {
777        match self {
778            Self::InvalidValue(s) => AlignSelfParseError::InvalidValue(s.as_str()),
779        }
780    }
781}
782
783#[cfg(feature = "parser")]
784/// # Errors
785///
786/// Returns an error if `input` is not a valid CSS `align-self` value.
787pub fn parse_layout_align_self(
788    input: &str,
789) -> Result<LayoutAlignSelf, AlignSelfParseError<'_>> {
790    match input.trim() {
791        "auto" => Ok(LayoutAlignSelf::Auto),
792        "stretch" => Ok(LayoutAlignSelf::Stretch),
793        "center" => Ok(LayoutAlignSelf::Center),
794        "start" | "flex-start" => Ok(LayoutAlignSelf::Start),
795        "end" | "flex-end" => Ok(LayoutAlignSelf::End),
796        "baseline" => Ok(LayoutAlignSelf::Baseline),
797        _ => Err(AlignSelfParseError::InvalidValue(input)),
798    }
799}
800
801// --- flex-basis ---
802#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
803/// Represents a `flex-basis` attribute
804#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
805#[repr(C, u8)]
806#[derive(Default)]
807pub enum LayoutFlexBasis {
808    /// auto
809    #[default]
810    Auto,
811    /// Fixed size
812    Exact(crate::props::basic::pixel::PixelValue),
813}
814
815impl core::fmt::Debug for LayoutFlexBasis {
816    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
817        write!(f, "{}", self.print_as_css_value())
818    }
819}
820
821
822impl PrintAsCssValue for LayoutFlexBasis {
823    fn print_as_css_value(&self) -> String {
824        match self {
825            Self::Auto => "auto".to_string(),
826            Self::Exact(px) => px.print_as_css_value(),
827        }
828    }
829}
830
831impl FormatAsRustCode for LayoutFlexBasis {
832    fn format_as_rust_code(&self, _tabs: usize) -> String {
833        match self {
834            Self::Auto => String::from("LayoutFlexBasis::Auto"),
835            Self::Exact(px) => {
836                format!(
837                    "LayoutFlexBasis::Exact({})",
838                    crate::codegen::format::format_pixel_value(px)
839                )
840            }
841        }
842    }
843}
844
845#[cfg(feature = "parser")]
846#[derive(Clone, PartialEq, Eq)]
847pub enum FlexBasisParseError<'a> {
848    InvalidValue(&'a str),
849}
850
851#[cfg(feature = "parser")]
852impl_debug_as_display!(FlexBasisParseError<'a>);
853#[cfg(feature = "parser")]
854impl_display! { FlexBasisParseError<'a>, {
855    InvalidValue(e) => format!("Invalid flex-basis value: \"{}\"", e),
856}}
857
858#[cfg(feature = "parser")]
859#[derive(Debug, Clone, PartialEq, Eq)]
860#[repr(C, u8)]
861pub enum FlexBasisParseErrorOwned {
862    InvalidValue(AzString),
863}
864
865#[cfg(feature = "parser")]
866impl FlexBasisParseError<'_> {
867    #[must_use] pub fn to_contained(&self) -> FlexBasisParseErrorOwned {
868        match self {
869            FlexBasisParseError::InvalidValue(s) => {
870                FlexBasisParseErrorOwned::InvalidValue((*s).to_string().into())
871            }
872        }
873    }
874}
875
876#[cfg(feature = "parser")]
877impl FlexBasisParseErrorOwned {
878    #[must_use] pub fn to_shared(&self) -> FlexBasisParseError<'_> {
879        match self {
880            Self::InvalidValue(s) => {
881                FlexBasisParseError::InvalidValue(s.as_str())
882            }
883        }
884    }
885}
886
887#[cfg(feature = "parser")]
888/// # Errors
889///
890/// Returns an error if `input` is not a valid CSS `flex-basis` value.
891pub fn parse_layout_flex_basis(
892    input: &str,
893) -> Result<LayoutFlexBasis, FlexBasisParseError<'_>> {
894    use crate::props::basic::pixel::parse_pixel_value;
895
896    match input.trim() {
897        "auto" => Ok(LayoutFlexBasis::Auto),
898        s => parse_pixel_value(s)
899            .map(LayoutFlexBasis::Exact)
900            .map_err(|_| FlexBasisParseError::InvalidValue(input)),
901    }
902}
903
904#[cfg(all(test, feature = "parser"))]
905mod tests {
906    // Tests assert that parsed values equal the exact source literals.
907    #![allow(clippy::float_cmp)]
908    use super::*;
909    use crate::props::basic::pixel::PixelValue;
910
911    #[test]
912    fn test_parse_layout_flex_grow() {
913        assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
914        assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
915        assert_eq!(parse_layout_flex_grow("2.5").unwrap().inner.get(), 2.5);
916        assert_eq!(parse_layout_flex_grow("  0.5  ").unwrap().inner.get(), 0.5);
917        assert!(parse_layout_flex_grow("none").is_err());
918        assert!(parse_layout_flex_grow("-1").is_err()); // Negative values are invalid
919    }
920
921    #[test]
922    fn test_parse_layout_flex_shrink() {
923        assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
924        assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
925        assert_eq!(parse_layout_flex_shrink("3.0").unwrap().inner.get(), 3.0);
926        assert_eq!(parse_layout_flex_shrink(" 0.2 ").unwrap().inner.get(), 0.2);
927        assert!(parse_layout_flex_shrink("auto").is_err());
928        assert!(parse_layout_flex_shrink("-1").is_err()); // Negative values are invalid
929    }
930
931    #[test]
932    fn test_parse_layout_flex_direction() {
933        assert_eq!(
934            parse_layout_flex_direction("row").unwrap(),
935            LayoutFlexDirection::Row
936        );
937        assert_eq!(
938            parse_layout_flex_direction("row-reverse").unwrap(),
939            LayoutFlexDirection::RowReverse
940        );
941        assert_eq!(
942            parse_layout_flex_direction("column").unwrap(),
943            LayoutFlexDirection::Column
944        );
945        assert_eq!(
946            parse_layout_flex_direction("column-reverse").unwrap(),
947            LayoutFlexDirection::ColumnReverse
948        );
949        assert_eq!(
950            parse_layout_flex_direction("  row  ").unwrap(),
951            LayoutFlexDirection::Row
952        );
953        assert!(parse_layout_flex_direction("reversed-row").is_err());
954    }
955
956    #[test]
957    fn test_parse_layout_flex_wrap() {
958        assert_eq!(
959            parse_layout_flex_wrap("nowrap").unwrap(),
960            LayoutFlexWrap::NoWrap
961        );
962        assert_eq!(
963            parse_layout_flex_wrap("wrap").unwrap(),
964            LayoutFlexWrap::Wrap
965        );
966        assert_eq!(
967            parse_layout_flex_wrap("wrap-reverse").unwrap(),
968            LayoutFlexWrap::WrapReverse
969        );
970        assert_eq!(
971            parse_layout_flex_wrap("  wrap  ").unwrap(),
972            LayoutFlexWrap::Wrap
973        );
974        assert!(parse_layout_flex_wrap("wrap reverse").is_err());
975    }
976
977    #[test]
978    fn test_parse_layout_justify_content() {
979        assert_eq!(
980            parse_layout_justify_content("flex-start").unwrap(),
981            LayoutJustifyContent::FlexStart
982        );
983        assert_eq!(
984            parse_layout_justify_content("flex-end").unwrap(),
985            LayoutJustifyContent::FlexEnd
986        );
987        assert_eq!(
988            parse_layout_justify_content("start").unwrap(),
989            LayoutJustifyContent::Start
990        );
991        assert_eq!(
992            parse_layout_justify_content("end").unwrap(),
993            LayoutJustifyContent::End
994        );
995        assert_eq!(
996            parse_layout_justify_content("center").unwrap(),
997            LayoutJustifyContent::Center
998        );
999        assert_eq!(
1000            parse_layout_justify_content("space-between").unwrap(),
1001            LayoutJustifyContent::SpaceBetween
1002        );
1003        assert_eq!(
1004            parse_layout_justify_content("space-around").unwrap(),
1005            LayoutJustifyContent::SpaceAround
1006        );
1007        assert_eq!(
1008            parse_layout_justify_content("space-evenly").unwrap(),
1009            LayoutJustifyContent::SpaceEvenly
1010        );
1011        assert_eq!(
1012            parse_layout_justify_content("  center  ").unwrap(),
1013            LayoutJustifyContent::Center
1014        );
1015    }
1016
1017    #[test]
1018    fn test_parse_layout_align_items() {
1019        assert_eq!(
1020            parse_layout_align_items("stretch").unwrap(),
1021            LayoutAlignItems::Stretch
1022        );
1023        assert_eq!(
1024            parse_layout_align_items("flex-start").unwrap(),
1025            LayoutAlignItems::Start
1026        );
1027        assert_eq!(
1028            parse_layout_align_items("flex-end").unwrap(),
1029            LayoutAlignItems::End
1030        );
1031        assert_eq!(
1032            parse_layout_align_items("start").unwrap(),
1033            LayoutAlignItems::Start
1034        );
1035        assert_eq!(
1036            parse_layout_align_items("end").unwrap(),
1037            LayoutAlignItems::End
1038        );
1039        assert_eq!(
1040            parse_layout_align_items("center").unwrap(),
1041            LayoutAlignItems::Center
1042        );
1043        assert_eq!(
1044            parse_layout_align_items("baseline").unwrap(),
1045            LayoutAlignItems::Baseline
1046        );
1047        assert!(parse_layout_align_items("invalid").is_err());
1048    }
1049
1050    #[test]
1051    fn test_parse_layout_align_content() {
1052        assert_eq!(
1053            parse_layout_align_content("stretch").unwrap(),
1054            LayoutAlignContent::Stretch
1055        );
1056        assert_eq!(
1057            parse_layout_align_content("flex-start").unwrap(),
1058            LayoutAlignContent::Start
1059        );
1060        assert_eq!(
1061            parse_layout_align_content("flex-end").unwrap(),
1062            LayoutAlignContent::End
1063        );
1064        assert_eq!(
1065            parse_layout_align_content("center").unwrap(),
1066            LayoutAlignContent::Center
1067        );
1068        assert_eq!(
1069            parse_layout_align_content("space-between").unwrap(),
1070            LayoutAlignContent::SpaceBetween
1071        );
1072        assert_eq!(
1073            parse_layout_align_content("space-around").unwrap(),
1074            LayoutAlignContent::SpaceAround
1075        );
1076        assert!(parse_layout_align_content("space-evenly").is_err()); // Not valid for align-content
1077    }
1078
1079    #[test]
1080    fn test_parse_layout_flex_basis() {
1081        assert_eq!(
1082            parse_layout_flex_basis("auto").unwrap(),
1083            LayoutFlexBasis::Auto
1084        );
1085        assert_eq!(
1086            parse_layout_flex_basis("200px").unwrap(),
1087            LayoutFlexBasis::Exact(PixelValue::px(200.0))
1088        );
1089        assert_eq!(
1090            parse_layout_flex_basis("50%").unwrap(),
1091            LayoutFlexBasis::Exact(PixelValue::percent(50.0))
1092        );
1093        assert_eq!(
1094            parse_layout_flex_basis("  10em  ").unwrap(),
1095            LayoutFlexBasis::Exact(PixelValue::em(10.0))
1096        );
1097        assert!(parse_layout_flex_basis("none").is_err());
1098        // Liberal parsing accepts unitless numbers (treated as px)
1099        assert_eq!(
1100            parse_layout_flex_basis("200").unwrap(),
1101            LayoutFlexBasis::Exact(PixelValue::px(200.0))
1102        );
1103        assert_eq!(
1104            parse_layout_flex_basis("0").unwrap(),
1105            LayoutFlexBasis::Exact(PixelValue::px(0.0))
1106        );
1107    }
1108}