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