Skip to main content

azul_css/props/layout/
grid.rs

1//! CSS properties for CSS Grid layout.
2
3use alloc::{
4    boxed::Box,
5    string::{String, ToString},
6    vec::Vec,
7};
8
9use crate::{
10    corety::AzString,
11    format_rust_code::FormatAsRustCode,
12    impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_eq, impl_vec_hash, impl_vec_mut,
13    impl_vec_ord, impl_vec_partialeq, impl_vec_partialord,
14    props::{basic::pixel::PixelValue, formatter::PrintAsCssValue},
15};
16
17// --- grid-template-columns / grid-template-rows ---
18
19/// Wrapper for minmax(min, max) to satisfy repr(C) (enum variants can only have 1 field)
20#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[repr(C)]
22pub struct GridMinMax {
23    pub min: Box<GridTrackSizing>,
24    pub max: Box<GridTrackSizing>,
25}
26
27impl core::fmt::Debug for GridMinMax {
28    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
29        write!(
30            f,
31            "minmax({}, {})",
32            self.min.print_as_css_value(),
33            self.max.print_as_css_value()
34        )
35    }
36}
37
38/// Represents a single track sizing function for grid
39#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C, u8)]
41pub enum GridTrackSizing {
42    /// Fixed pixel/percent size
43    Fixed(PixelValue),
44    /// fr units (stored as integer to satisfy Eq/Ord/Hash)
45    Fr(i32),
46    /// min-content
47    MinContent,
48    /// max-content
49    MaxContent,
50    /// auto
51    Auto,
52    /// minmax(min, max) - uses GridMinMax which contains Box<GridTrackSizing> for each bound
53    MinMax(GridMinMax),
54    /// fit-content(size)
55    FitContent(PixelValue),
56}
57
58impl core::fmt::Debug for GridTrackSizing {
59    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
60        write!(f, "{}", self.print_as_css_value())
61    }
62}
63
64impl Default for GridTrackSizing {
65    fn default() -> Self {
66        GridTrackSizing::Auto
67    }
68}
69
70impl PrintAsCssValue for GridTrackSizing {
71    fn print_as_css_value(&self) -> String {
72        match self {
73            GridTrackSizing::Fixed(px) => px.print_as_css_value(),
74            GridTrackSizing::Fr(f) => format!("{}fr", f),
75            GridTrackSizing::MinContent => "min-content".to_string(),
76            GridTrackSizing::MaxContent => "max-content".to_string(),
77            GridTrackSizing::Auto => "auto".to_string(),
78            GridTrackSizing::MinMax(minmax) => {
79                format!(
80                    "minmax({}, {})",
81                    minmax.min.print_as_css_value(),
82                    minmax.max.print_as_css_value()
83                )
84            }
85            GridTrackSizing::FitContent(size) => {
86                format!("fit-content({})", size.print_as_css_value())
87            }
88        }
89    }
90}
91
92// C-compatible Vec for GridTrackSizing
93impl_vec!(
94    GridTrackSizing,
95    GridTrackSizingVec,
96    GridTrackSizingVecDestructor,
97    GridTrackSizingVecDestructorType
98);
99impl_vec_clone!(
100    GridTrackSizing,
101    GridTrackSizingVec,
102    GridTrackSizingVecDestructor
103);
104impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
105impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
106impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
107impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
108impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
109impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
110impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
111
112/// Represents `grid-template-columns` or `grid-template-rows`
113#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[repr(C)]
115pub struct GridTemplate {
116    pub tracks: GridTrackSizingVec,
117}
118
119impl core::fmt::Debug for GridTemplate {
120    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
121        write!(f, "{}", self.print_as_css_value())
122    }
123}
124
125impl Default for GridTemplate {
126    fn default() -> Self {
127        GridTemplate {
128            tracks: GridTrackSizingVec::from_vec(Vec::new()),
129        }
130    }
131}
132
133impl PrintAsCssValue for GridTemplate {
134    fn print_as_css_value(&self) -> String {
135        let tracks_slice = self.tracks.as_ref();
136        if tracks_slice.is_empty() {
137            "none".to_string()
138        } else {
139            tracks_slice
140                .iter()
141                .map(|t| t.print_as_css_value())
142                .collect::<Vec<_>>()
143                .join(" ")
144        }
145    }
146}
147
148// --- grid-auto-columns / grid-auto-rows ---
149
150/// Represents `grid-auto-columns` or `grid-auto-rows`
151/// Structurally identical to GridTemplate but semantically different
152#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
153#[repr(C)]
154pub struct GridAutoTracks {
155    pub tracks: GridTrackSizingVec,
156}
157
158impl core::fmt::Debug for GridAutoTracks {
159    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
160        write!(f, "{}", self.print_as_css_value())
161    }
162}
163
164impl Default for GridAutoTracks {
165    fn default() -> Self {
166        GridAutoTracks {
167            tracks: GridTrackSizingVec::from_vec(Vec::new()),
168        }
169    }
170}
171
172impl PrintAsCssValue for GridAutoTracks {
173    fn print_as_css_value(&self) -> String {
174        let tracks_slice = self.tracks.as_ref();
175        if tracks_slice.is_empty() {
176            "auto".to_string()
177        } else {
178            tracks_slice
179                .iter()
180                .map(|t| t.print_as_css_value())
181                .collect::<Vec<_>>()
182                .join(" ")
183        }
184    }
185}
186
187impl From<GridTemplate> for GridAutoTracks {
188    fn from(template: GridTemplate) -> Self {
189        GridAutoTracks {
190            tracks: template.tracks,
191        }
192    }
193}
194
195// --- grid-row / grid-column (grid line placement) ---
196
197/// Named grid line with optional span count (FFI-safe wrapper)
198#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
199#[repr(C)]
200pub struct NamedGridLine {
201    pub grid_line_name: AzString,
202    /// Span count, 0 means no span specified
203    pub span_count: i32,
204}
205
206impl NamedGridLine {
207    pub fn create(name: AzString, span: Option<i32>) -> Self {
208        Self {
209            grid_line_name: name,
210            span_count: span.unwrap_or(0),
211        }
212    }
213
214    pub fn span(&self) -> Option<i32> {
215        if self.span_count == 0 {
216            None
217        } else {
218            Some(self.span_count)
219        }
220    }
221}
222
223/// Represents a grid line position (start or end)
224#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
225#[repr(C, u8)]
226pub enum GridLine {
227    /// auto
228    Auto,
229    /// Line number (1-based, negative for counting from end)
230    Line(i32),
231    /// Named line with optional span count
232    Named(NamedGridLine),
233    /// span N
234    Span(i32),
235}
236
237impl core::fmt::Debug for GridLine {
238    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
239        write!(f, "{}", self.print_as_css_value())
240    }
241}
242
243impl Default for GridLine {
244    fn default() -> Self {
245        GridLine::Auto
246    }
247}
248
249impl PrintAsCssValue for GridLine {
250    fn print_as_css_value(&self) -> String {
251        match self {
252            GridLine::Auto => "auto".to_string(),
253            GridLine::Line(n) => n.to_string(),
254            GridLine::Named(named) => {
255                if named.span_count == 0 {
256                    named.grid_line_name.as_str().to_string()
257                } else {
258                    format!("{} {}", named.grid_line_name.as_str(), named.span_count)
259                }
260            }
261            GridLine::Span(n) => format!("span {}", n),
262        }
263    }
264}
265
266/// Represents `grid-row` or `grid-column` (start / end)
267#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268#[repr(C)]
269pub struct GridPlacement {
270    pub grid_start: GridLine,
271    pub grid_end: GridLine,
272}
273
274impl core::fmt::Debug for GridPlacement {
275    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
276        write!(f, "{}", self.print_as_css_value())
277    }
278}
279
280impl Default for GridPlacement {
281    fn default() -> Self {
282        GridPlacement {
283            grid_start: GridLine::Auto,
284            grid_end: GridLine::Auto,
285        }
286    }
287}
288
289impl PrintAsCssValue for GridPlacement {
290    fn print_as_css_value(&self) -> String {
291        if self.grid_end == GridLine::Auto {
292            self.grid_start.print_as_css_value()
293        } else {
294            format!(
295                "{} / {}",
296                self.grid_start.print_as_css_value(),
297                self.grid_end.print_as_css_value()
298            )
299        }
300    }
301}
302
303#[cfg(feature = "parser")]
304#[derive(Clone, PartialEq)]
305pub enum GridParseError<'a> {
306    InvalidValue(&'a str),
307}
308
309#[cfg(feature = "parser")]
310impl_debug_as_display!(GridParseError<'a>);
311#[cfg(feature = "parser")]
312impl_display! { GridParseError<'a>, {
313    InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
314}}
315
316#[cfg(feature = "parser")]
317#[derive(Debug, Clone, PartialEq)]
318pub enum GridParseErrorOwned {
319    InvalidValue(String),
320}
321
322#[cfg(feature = "parser")]
323impl<'a> GridParseError<'a> {
324    pub fn to_contained(&self) -> GridParseErrorOwned {
325        match self {
326            GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue(s.to_string()),
327        }
328    }
329}
330
331#[cfg(feature = "parser")]
332impl GridParseErrorOwned {
333    pub fn to_shared<'a>(&'a self) -> GridParseError<'a> {
334        match self {
335            GridParseErrorOwned::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
336        }
337    }
338}
339
340#[cfg(feature = "parser")]
341pub fn parse_grid_template<'a>(input: &'a str) -> Result<GridTemplate, GridParseError<'a>> {
342    use crate::props::basic::pixel::parse_pixel_value;
343
344    let input = input.trim();
345
346    if input == "none" {
347        return Ok(GridTemplate::default());
348    }
349
350    let mut tracks = Vec::new();
351    let mut current = String::new();
352    let mut paren_depth = 0;
353
354    for ch in input.chars() {
355        match ch {
356            '(' => {
357                paren_depth += 1;
358                current.push(ch);
359            }
360            ')' => {
361                paren_depth -= 1;
362                current.push(ch);
363            }
364            ' ' if paren_depth == 0 => {
365                if !current.trim().is_empty() {
366                    let track_str = current.trim().to_string();
367                    tracks.push(
368                        parse_grid_track_owned(&track_str)
369                            .map_err(|_| GridParseError::InvalidValue(input))?,
370                    );
371                    current.clear();
372                }
373            }
374            _ => current.push(ch),
375        }
376    }
377
378    if !current.trim().is_empty() {
379        let track_str = current.trim().to_string();
380        tracks.push(
381            parse_grid_track_owned(&track_str).map_err(|_| GridParseError::InvalidValue(input))?,
382        );
383    }
384
385    Ok(GridTemplate {
386        tracks: GridTrackSizingVec::from_vec(tracks),
387    })
388}
389
390#[cfg(feature = "parser")]
391fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
392    use crate::props::basic::pixel::parse_pixel_value;
393
394    let input = input.trim();
395
396    if input == "auto" {
397        return Ok(GridTrackSizing::Auto);
398    }
399
400    if input == "min-content" {
401        return Ok(GridTrackSizing::MinContent);
402    }
403
404    if input == "max-content" {
405        return Ok(GridTrackSizing::MaxContent);
406    }
407
408    if input.ends_with("fr") {
409        let num_str = &input[..input.len() - 2].trim();
410        if let Ok(num) = num_str.parse::<f32>() {
411            return Ok(GridTrackSizing::Fr((num * 100.0) as i32));
412        }
413        return Err(());
414    }
415
416    if input.starts_with("minmax(") && input.ends_with(')') {
417        let content = &input[7..input.len() - 1];
418        let parts: Vec<&str> = content.split(',').collect();
419        if parts.len() == 2 {
420            let min = parse_grid_track_owned(parts[0].trim())?;
421            let max = parse_grid_track_owned(parts[1].trim())?;
422            return Ok(GridTrackSizing::MinMax(GridMinMax {
423                min: Box::new(min),
424                max: Box::new(max),
425            }));
426        }
427        return Err(());
428    }
429
430    if input.starts_with("fit-content(") && input.ends_with(')') {
431        let size_str = &input[12..input.len() - 1].trim();
432        if let Ok(size) = parse_pixel_value(size_str) {
433            return Ok(GridTrackSizing::FitContent(size));
434        }
435        return Err(());
436    }
437
438    // Try to parse as pixel value
439    if let Ok(px) = parse_pixel_value(input) {
440        return Ok(GridTrackSizing::Fixed(px));
441    }
442
443    Err(())
444}
445
446#[cfg(feature = "parser")]
447pub fn parse_grid_placement<'a>(input: &'a str) -> Result<GridPlacement, GridParseError<'a>> {
448    let input = input.trim();
449
450    if input == "auto" {
451        return Ok(GridPlacement::default());
452    }
453
454    // Split by "/"
455    let parts: Vec<&str> = input.split('/').map(|s| s.trim()).collect();
456
457    let grid_start =
458        parse_grid_line_owned(parts[0]).map_err(|_| GridParseError::InvalidValue(input))?;
459    let grid_end = if parts.len() > 1 {
460        parse_grid_line_owned(parts[1]).map_err(|_| GridParseError::InvalidValue(input))?
461    } else {
462        GridLine::Auto
463    };
464
465    Ok(GridPlacement {
466        grid_start,
467        grid_end,
468    })
469}
470
471// --- grid-auto-flow ---
472
473/// Represents the `grid-auto-flow` property
474#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
475#[repr(C)]
476pub enum LayoutGridAutoFlow {
477    Row,
478    Column,
479    RowDense,
480    ColumnDense,
481}
482
483impl Default for LayoutGridAutoFlow {
484    fn default() -> Self {
485        LayoutGridAutoFlow::Row
486    }
487}
488
489impl crate::props::formatter::PrintAsCssValue for LayoutGridAutoFlow {
490    fn print_as_css_value(&self) -> alloc::string::String {
491        match self {
492            LayoutGridAutoFlow::Row => "row".to_string(),
493            LayoutGridAutoFlow::Column => "column".to_string(),
494            LayoutGridAutoFlow::RowDense => "row dense".to_string(),
495            LayoutGridAutoFlow::ColumnDense => "column dense".to_string(),
496        }
497    }
498}
499
500#[cfg(feature = "parser")]
501#[derive(Clone, PartialEq)]
502pub enum GridAutoFlowParseError<'a> {
503    InvalidValue(&'a str),
504}
505
506#[cfg(feature = "parser")]
507impl_debug_as_display!(GridAutoFlowParseError<'a>);
508#[cfg(feature = "parser")]
509impl_display! { GridAutoFlowParseError<'a>, {
510    InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
511}}
512
513#[cfg(feature = "parser")]
514#[derive(Debug, Clone, PartialEq)]
515pub enum GridAutoFlowParseErrorOwned {
516    InvalidValue(alloc::string::String),
517}
518
519#[cfg(feature = "parser")]
520impl<'a> GridAutoFlowParseError<'a> {
521    pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
522        match self {
523            GridAutoFlowParseError::InvalidValue(s) => {
524                GridAutoFlowParseErrorOwned::InvalidValue(s.to_string())
525            }
526        }
527    }
528}
529
530#[cfg(feature = "parser")]
531impl GridAutoFlowParseErrorOwned {
532    pub fn to_shared<'a>(&'a self) -> GridAutoFlowParseError<'a> {
533        match self {
534            GridAutoFlowParseErrorOwned::InvalidValue(s) => {
535                GridAutoFlowParseError::InvalidValue(s.as_str())
536            }
537        }
538    }
539}
540
541#[cfg(feature = "parser")]
542pub fn parse_layout_grid_auto_flow<'a>(
543    input: &'a str,
544) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'a>> {
545    match input.trim() {
546        "row" => Ok(LayoutGridAutoFlow::Row),
547        "column" => Ok(LayoutGridAutoFlow::Column),
548        "row dense" => Ok(LayoutGridAutoFlow::RowDense),
549        "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
550        "dense" => Ok(LayoutGridAutoFlow::RowDense),
551        _ => Err(GridAutoFlowParseError::InvalidValue(input)),
552    }
553}
554
555// --- justify-self / justify-items ---
556
557/// Represents `justify-self` for grid items
558#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
559#[repr(C)]
560pub enum LayoutJustifySelf {
561    Auto,
562    Start,
563    End,
564    Center,
565    Stretch,
566}
567
568impl Default for LayoutJustifySelf {
569    fn default() -> Self {
570        Self::Auto
571    }
572}
573
574impl crate::props::formatter::PrintAsCssValue for LayoutJustifySelf {
575    fn print_as_css_value(&self) -> alloc::string::String {
576        match self {
577            LayoutJustifySelf::Auto => "auto".to_string(),
578            LayoutJustifySelf::Start => "start".to_string(),
579            LayoutJustifySelf::End => "end".to_string(),
580            LayoutJustifySelf::Center => "center".to_string(),
581            LayoutJustifySelf::Stretch => "stretch".to_string(),
582        }
583    }
584}
585
586#[cfg(feature = "parser")]
587#[derive(Clone, PartialEq)]
588pub enum JustifySelfParseError<'a> {
589    InvalidValue(&'a str),
590}
591
592#[cfg(feature = "parser")]
593#[derive(Debug, Clone, PartialEq)]
594pub enum JustifySelfParseErrorOwned {
595    InvalidValue(alloc::string::String),
596}
597
598#[cfg(feature = "parser")]
599impl<'a> JustifySelfParseError<'a> {
600    pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
601        match self {
602            JustifySelfParseError::InvalidValue(s) => {
603                JustifySelfParseErrorOwned::InvalidValue(s.to_string())
604            }
605        }
606    }
607}
608
609#[cfg(feature = "parser")]
610impl JustifySelfParseErrorOwned {
611    pub fn to_shared<'a>(&'a self) -> JustifySelfParseError<'a> {
612        match self {
613            JustifySelfParseErrorOwned::InvalidValue(s) => {
614                JustifySelfParseError::InvalidValue(s.as_str())
615            }
616        }
617    }
618}
619
620#[cfg(feature = "parser")]
621impl_debug_as_display!(JustifySelfParseError<'a>);
622#[cfg(feature = "parser")]
623impl_display! { JustifySelfParseError<'a>, {
624    InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
625}}
626
627#[cfg(feature = "parser")]
628pub fn parse_layout_justify_self<'a>(
629    input: &'a str,
630) -> Result<LayoutJustifySelf, JustifySelfParseError<'a>> {
631    match input.trim() {
632        "auto" => Ok(LayoutJustifySelf::Auto),
633        "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
634        "end" | "flex-end" => Ok(LayoutJustifySelf::End),
635        "center" => Ok(LayoutJustifySelf::Center),
636        "stretch" => Ok(LayoutJustifySelf::Stretch),
637        _ => Err(JustifySelfParseError::InvalidValue(input)),
638    }
639}
640
641/// Represents `justify-items` for grid containers
642#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
643#[repr(C)]
644pub enum LayoutJustifyItems {
645    Start,
646    End,
647    Center,
648    Stretch,
649}
650
651impl Default for LayoutJustifyItems {
652    fn default() -> Self {
653        Self::Stretch
654    }
655}
656
657impl crate::props::formatter::PrintAsCssValue for LayoutJustifyItems {
658    fn print_as_css_value(&self) -> alloc::string::String {
659        match self {
660            LayoutJustifyItems::Start => "start".to_string(),
661            LayoutJustifyItems::End => "end".to_string(),
662            LayoutJustifyItems::Center => "center".to_string(),
663            LayoutJustifyItems::Stretch => "stretch".to_string(),
664        }
665    }
666}
667
668#[cfg(feature = "parser")]
669#[derive(Clone, PartialEq)]
670pub enum JustifyItemsParseError<'a> {
671    InvalidValue(&'a str),
672}
673
674#[cfg(feature = "parser")]
675#[derive(Debug, Clone, PartialEq)]
676pub enum JustifyItemsParseErrorOwned {
677    InvalidValue(alloc::string::String),
678}
679
680#[cfg(feature = "parser")]
681impl<'a> JustifyItemsParseError<'a> {
682    pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
683        match self {
684            JustifyItemsParseError::InvalidValue(s) => {
685                JustifyItemsParseErrorOwned::InvalidValue(s.to_string())
686            }
687        }
688    }
689}
690
691#[cfg(feature = "parser")]
692impl JustifyItemsParseErrorOwned {
693    pub fn to_shared<'a>(&'a self) -> JustifyItemsParseError<'a> {
694        match self {
695            JustifyItemsParseErrorOwned::InvalidValue(s) => {
696                JustifyItemsParseError::InvalidValue(s.as_str())
697            }
698        }
699    }
700}
701
702#[cfg(feature = "parser")]
703impl_debug_as_display!(JustifyItemsParseError<'a>);
704#[cfg(feature = "parser")]
705impl_display! { JustifyItemsParseError<'a>, {
706    InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
707}}
708
709#[cfg(feature = "parser")]
710pub fn parse_layout_justify_items<'a>(
711    input: &'a str,
712) -> Result<LayoutJustifyItems, JustifyItemsParseError<'a>> {
713    match input.trim() {
714        "start" => Ok(LayoutJustifyItems::Start),
715        "end" => Ok(LayoutJustifyItems::End),
716        "center" => Ok(LayoutJustifyItems::Center),
717        "stretch" => Ok(LayoutJustifyItems::Stretch),
718        _ => Err(JustifyItemsParseError::InvalidValue(input)),
719    }
720}
721
722// --- gap (single value type) ---
723
724#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
725#[repr(C)]
726pub struct LayoutGap {
727    pub inner: crate::props::basic::pixel::PixelValue,
728}
729
730impl core::fmt::Debug for LayoutGap {
731    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
732        write!(f, "{}", self.inner)
733    }
734}
735
736impl crate::props::formatter::PrintAsCssValue for LayoutGap {
737    fn print_as_css_value(&self) -> alloc::string::String {
738        self.inner.print_as_css_value()
739    }
740}
741
742// Implement FormatAsRustCode for the new types so they can be emitted by the
743// code generator.
744impl FormatAsRustCode for LayoutGridAutoFlow {
745    fn format_as_rust_code(&self, _tabs: usize) -> String {
746        format!(
747            "LayoutGridAutoFlow::{}",
748            match self {
749                LayoutGridAutoFlow::Row => "Row",
750                LayoutGridAutoFlow::Column => "Column",
751                LayoutGridAutoFlow::RowDense => "RowDense",
752                LayoutGridAutoFlow::ColumnDense => "ColumnDense",
753            }
754        )
755    }
756}
757
758impl FormatAsRustCode for LayoutJustifySelf {
759    fn format_as_rust_code(&self, _tabs: usize) -> String {
760        format!(
761            "LayoutJustifySelf::{}",
762            match self {
763                LayoutJustifySelf::Auto => "Auto",
764                LayoutJustifySelf::Start => "Start",
765                LayoutJustifySelf::End => "End",
766                LayoutJustifySelf::Center => "Center",
767                LayoutJustifySelf::Stretch => "Stretch",
768            }
769        )
770    }
771}
772
773impl FormatAsRustCode for LayoutJustifyItems {
774    fn format_as_rust_code(&self, _tabs: usize) -> String {
775        format!(
776            "LayoutJustifyItems::{}",
777            match self {
778                LayoutJustifyItems::Start => "Start",
779                LayoutJustifyItems::End => "End",
780                LayoutJustifyItems::Center => "Center",
781                LayoutJustifyItems::Stretch => "Stretch",
782            }
783        )
784    }
785}
786
787impl FormatAsRustCode for LayoutGap {
788    fn format_as_rust_code(&self, _tabs: usize) -> String {
789        // LayoutGap wraps a PixelValue which implements FormatAsRustCode via helpers;
790        // print as LayoutGap::Exact(LAYERVALUE) is not required here — use the CSS string
791        format!("LayoutGap::Exact({})", self.inner)
792    }
793}
794
795impl FormatAsRustCode for GridTrackSizing {
796    fn format_as_rust_code(&self, tabs: usize) -> String {
797        use crate::format_rust_code::format_pixel_value;
798        match self {
799            GridTrackSizing::Fixed(pv) => {
800                format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
801            }
802            GridTrackSizing::Fr(f) => format!("GridTrackSizing::Fr({})", f),
803            GridTrackSizing::MinContent => "GridTrackSizing::MinContent".to_string(),
804            GridTrackSizing::MaxContent => "GridTrackSizing::MaxContent".to_string(),
805            GridTrackSizing::Auto => "GridTrackSizing::Auto".to_string(),
806            GridTrackSizing::MinMax(minmax) => {
807                format!(
808                    "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
809                    minmax.min.format_as_rust_code(tabs),
810                    minmax.max.format_as_rust_code(tabs)
811                )
812            }
813            GridTrackSizing::FitContent(pv) => {
814                format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
815            }
816        }
817    }
818}
819
820impl FormatAsRustCode for GridAutoTracks {
821    fn format_as_rust_code(&self, tabs: usize) -> String {
822        let tracks: Vec<String> = self
823            .tracks
824            .as_ref()
825            .iter()
826            .map(|t| t.format_as_rust_code(tabs))
827            .collect();
828        format!(
829            "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
830            tracks.join(", ")
831        )
832    }
833}
834
835#[cfg(feature = "parser")]
836pub fn parse_layout_gap<'a>(
837    input: &'a str,
838) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'a>> {
839    crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
840}
841
842#[cfg(feature = "parser")]
843fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
844    let input = input.trim();
845
846    if input == "auto" {
847        return Ok(GridLine::Auto);
848    }
849
850    if input.starts_with("span ") {
851        let num_str = &input[5..].trim();
852        if let Ok(num) = num_str.parse::<i32>() {
853            return Ok(GridLine::Span(num));
854        }
855        return Err(());
856    }
857
858    // Try to parse as line number
859    if let Ok(num) = input.parse::<i32>() {
860        return Ok(GridLine::Line(num));
861    }
862
863    // Otherwise treat as named line
864    Ok(GridLine::Named(NamedGridLine::create(
865        input.to_string().into(),
866        None,
867    )))
868}
869
870#[cfg(all(test, feature = "parser"))]
871mod tests {
872    use super::*;
873
874    // Grid template tests
875    #[test]
876    fn test_parse_grid_template_none() {
877        let result = parse_grid_template("none").unwrap();
878        assert_eq!(result.tracks.len(), 0);
879    }
880
881    #[test]
882    fn test_parse_grid_template_single_px() {
883        let result = parse_grid_template("100px").unwrap();
884        assert_eq!(result.tracks.len(), 1);
885        assert!(matches!(
886            result.tracks.as_ref()[0],
887            GridTrackSizing::Fixed(_)
888        ));
889    }
890
891    #[test]
892    fn test_parse_grid_template_multiple_tracks() {
893        let result = parse_grid_template("100px 200px 1fr").unwrap();
894        assert_eq!(result.tracks.len(), 3);
895    }
896
897    #[test]
898    fn test_parse_grid_template_fr_units() {
899        let result = parse_grid_template("1fr 2fr 1fr").unwrap();
900        assert_eq!(result.tracks.len(), 3);
901        assert!(matches!(
902            result.tracks.as_ref()[0],
903            GridTrackSizing::Fr(100)
904        ));
905        assert!(matches!(
906            result.tracks.as_ref()[1],
907            GridTrackSizing::Fr(200)
908        ));
909    }
910
911    #[test]
912    fn test_parse_grid_template_fractional_fr() {
913        let result = parse_grid_template("0.5fr 1.5fr").unwrap();
914        assert_eq!(result.tracks.len(), 2);
915        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
916        assert!(matches!(
917            result.tracks.as_ref()[1],
918            GridTrackSizing::Fr(150)
919        ));
920    }
921
922    #[test]
923    fn test_parse_grid_template_auto() {
924        let result = parse_grid_template("auto 100px auto").unwrap();
925        assert_eq!(result.tracks.len(), 3);
926        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
927        assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
928    }
929
930    #[test]
931    fn test_parse_grid_template_min_max_content() {
932        let result = parse_grid_template("min-content max-content auto").unwrap();
933        assert_eq!(result.tracks.len(), 3);
934        assert!(matches!(
935            result.tracks.as_ref()[0],
936            GridTrackSizing::MinContent
937        ));
938        assert!(matches!(
939            result.tracks.as_ref()[1],
940            GridTrackSizing::MaxContent
941        ));
942    }
943
944    #[test]
945    fn test_parse_grid_template_minmax() {
946        let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
947        assert_eq!(result.tracks.len(), 1);
948        assert!(matches!(
949            result.tracks.as_ref()[0],
950            GridTrackSizing::MinMax(_)
951        ));
952    }
953
954    #[test]
955    fn test_parse_grid_template_minmax_complex() {
956        let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
957        assert_eq!(result.tracks.len(), 1);
958    }
959
960    #[test]
961    fn test_parse_grid_template_fit_content() {
962        let result = parse_grid_template("fit-content(200px)").unwrap();
963        assert_eq!(result.tracks.len(), 1);
964        assert!(matches!(
965            result.tracks.as_ref()[0],
966            GridTrackSizing::FitContent(_)
967        ));
968    }
969
970    #[test]
971    fn test_parse_grid_template_mixed() {
972        let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
973        assert_eq!(result.tracks.len(), 4);
974    }
975
976    #[test]
977    fn test_parse_grid_template_percent() {
978        let result = parse_grid_template("25% 50% 25%").unwrap();
979        assert_eq!(result.tracks.len(), 3);
980    }
981
982    #[test]
983    fn test_parse_grid_template_em_units() {
984        let result = parse_grid_template("10em 20em 1fr").unwrap();
985        assert_eq!(result.tracks.len(), 3);
986    }
987
988    // Grid placement tests
989    #[test]
990    fn test_parse_grid_placement_auto() {
991        let result = parse_grid_placement("auto").unwrap();
992        assert!(matches!(result.grid_start, GridLine::Auto));
993        assert!(matches!(result.grid_end, GridLine::Auto));
994    }
995
996    #[test]
997    fn test_parse_grid_placement_line_number() {
998        let result = parse_grid_placement("1").unwrap();
999        assert!(matches!(result.grid_start, GridLine::Line(1)));
1000        assert!(matches!(result.grid_end, GridLine::Auto));
1001    }
1002
1003    #[test]
1004    fn test_parse_grid_placement_negative_line() {
1005        let result = parse_grid_placement("-1").unwrap();
1006        assert!(matches!(result.grid_start, GridLine::Line(-1)));
1007    }
1008
1009    #[test]
1010    fn test_parse_grid_placement_span() {
1011        let result = parse_grid_placement("span 2").unwrap();
1012        assert!(matches!(result.grid_start, GridLine::Span(2)));
1013    }
1014
1015    #[test]
1016    fn test_parse_grid_placement_start_end() {
1017        let result = parse_grid_placement("1 / 3").unwrap();
1018        assert!(matches!(result.grid_start, GridLine::Line(1)));
1019        assert!(matches!(result.grid_end, GridLine::Line(3)));
1020    }
1021
1022    #[test]
1023    fn test_parse_grid_placement_span_end() {
1024        let result = parse_grid_placement("1 / span 2").unwrap();
1025        assert!(matches!(result.grid_start, GridLine::Line(1)));
1026        assert!(matches!(result.grid_end, GridLine::Span(2)));
1027    }
1028
1029    #[test]
1030    fn test_parse_grid_placement_named_line() {
1031        let result = parse_grid_placement("header-start").unwrap();
1032        assert!(matches!(result.grid_start, GridLine::Named(_)));
1033    }
1034
1035    #[test]
1036    fn test_parse_grid_placement_named_start_end() {
1037        let result = parse_grid_placement("header-start / header-end").unwrap();
1038        assert!(matches!(result.grid_start, GridLine::Named(_)));
1039        assert!(matches!(result.grid_end, GridLine::Named(_)));
1040    }
1041
1042    // Edge cases
1043    #[test]
1044    fn test_parse_grid_template_whitespace() {
1045        let result = parse_grid_template("  100px   200px  ").unwrap();
1046        assert_eq!(result.tracks.len(), 2);
1047    }
1048
1049    #[test]
1050    fn test_parse_grid_placement_whitespace() {
1051        let result = parse_grid_placement("  1  /  3  ").unwrap();
1052        assert!(matches!(result.grid_start, GridLine::Line(1)));
1053        assert!(matches!(result.grid_end, GridLine::Line(3)));
1054    }
1055
1056    #[test]
1057    fn test_parse_grid_template_zero_fr() {
1058        let result = parse_grid_template("0fr").unwrap();
1059        assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1060    }
1061
1062    #[test]
1063    fn test_parse_grid_placement_zero_line() {
1064        let result = parse_grid_placement("0").unwrap();
1065        assert!(matches!(result.grid_start, GridLine::Line(0)));
1066    }
1067}