1use alloc::{
4 boxed::Box,
5 string::{String, ToString},
6 vec::Vec,
7};
8
9use crate::{
10 corety::AzString,
11 codegen::format::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#[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#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C, u8)]
41#[derive(Default)]
42pub enum GridTrackSizing {
43 Fixed(PixelValue),
45 Fr(i32),
48 MinContent,
50 MaxContent,
52 #[default]
54 Auto,
55 MinMax(GridMinMax),
57 FitContent(PixelValue),
59}
60
61impl_option!(
62 GridTrackSizing,
63 OptionGridTrackSizing,
64 copy = false,
65 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
66);
67
68impl core::fmt::Debug for GridTrackSizing {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 write!(f, "{}", self.print_as_css_value())
71 }
72}
73
74
75impl PrintAsCssValue for GridTrackSizing {
76 fn print_as_css_value(&self) -> String {
77 match self {
78 Self::Fixed(px) => px.print_as_css_value(),
79 Self::Fr(f) => format!("{f}fr"),
80 Self::MinContent => "min-content".to_string(),
81 Self::MaxContent => "max-content".to_string(),
82 Self::Auto => "auto".to_string(),
83 Self::MinMax(minmax) => {
84 format!(
85 "minmax({}, {})",
86 minmax.min.print_as_css_value(),
87 minmax.max.print_as_css_value()
88 )
89 }
90 Self::FitContent(size) => {
91 format!("fit-content({})", size.print_as_css_value())
92 }
93 }
94 }
95}
96
97impl_vec!(GridTrackSizing, GridTrackSizingVec, GridTrackSizingVecDestructor, GridTrackSizingVecDestructorType, GridTrackSizingVecSlice, OptionGridTrackSizing);
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#[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 Self {
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(PrintAsCssValue::print_as_css_value)
142 .collect::<Vec<_>>()
143 .join(" ")
144 }
145 }
146}
147
148#[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 Self {
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(PrintAsCssValue::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 Self {
190 tracks: template.tracks,
191 }
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
199#[repr(C)]
200pub struct NamedGridLine {
201 pub grid_line_name: AzString,
202 pub span_count: i32,
204}
205
206impl NamedGridLine {
207 #[must_use] 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 #[must_use] pub const fn span(&self) -> Option<i32> {
215 if self.span_count == 0 {
216 None
217 } else {
218 Some(self.span_count)
219 }
220 }
221}
222#[allow(variant_size_differences)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
225#[repr(C, u8)]
226#[derive(Default)]
227pub enum GridLine {
228 #[default]
230 Auto,
231 Line(i32),
233 Named(NamedGridLine),
235 Span(i32),
237}
238
239impl core::fmt::Debug for GridLine {
240 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241 write!(f, "{}", self.print_as_css_value())
242 }
243}
244
245
246impl PrintAsCssValue for GridLine {
247 fn print_as_css_value(&self) -> String {
248 match self {
249 Self::Auto => "auto".to_string(),
250 Self::Line(n) => n.to_string(),
251 Self::Named(named) => {
252 if named.span_count == 0 {
253 named.grid_line_name.as_str().to_string()
254 } else {
255 format!("{} {}", named.grid_line_name.as_str(), named.span_count)
256 }
257 }
258 Self::Span(n) => format!("span {n}"),
259 }
260 }
261}
262
263#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
265#[repr(C)]
266pub struct GridPlacement {
267 pub grid_start: GridLine,
268 pub grid_end: GridLine,
269}
270
271impl core::fmt::Debug for GridPlacement {
272 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273 write!(f, "{}", self.print_as_css_value())
274 }
275}
276
277impl Default for GridPlacement {
278 fn default() -> Self {
279 Self {
280 grid_start: GridLine::Auto,
281 grid_end: GridLine::Auto,
282 }
283 }
284}
285
286impl PrintAsCssValue for GridPlacement {
287 fn print_as_css_value(&self) -> String {
288 if self.grid_end == GridLine::Auto {
289 self.grid_start.print_as_css_value()
290 } else {
291 format!(
292 "{} / {}",
293 self.grid_start.print_as_css_value(),
294 self.grid_end.print_as_css_value()
295 )
296 }
297 }
298}
299
300#[cfg(feature = "parser")]
301#[derive(Clone, PartialEq, Eq)]
302pub enum GridParseError<'a> {
303 InvalidValue(&'a str),
304}
305
306#[cfg(feature = "parser")]
307impl_debug_as_display!(GridParseError<'a>);
308#[cfg(feature = "parser")]
309impl_display! { GridParseError<'a>, {
310 InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
311}}
312
313#[cfg(feature = "parser")]
314#[derive(Debug, Clone, PartialEq, Eq)]
315#[repr(C, u8)]
316pub enum GridParseErrorOwned {
317 InvalidValue(AzString),
318}
319
320#[cfg(feature = "parser")]
321impl GridParseError<'_> {
322 #[must_use] pub fn to_contained(&self) -> GridParseErrorOwned {
323 match self {
324 GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue((*s).to_string().into()),
325 }
326 }
327}
328
329#[cfg(feature = "parser")]
330impl GridParseErrorOwned {
331 #[must_use] pub fn to_shared(&self) -> GridParseError<'_> {
332 match self {
333 Self::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
334 }
335 }
336}
337
338#[cfg(feature = "parser")]
339fn split_respecting_parens(input: &str) -> Result<Vec<String>, ()> {
340 let mut parts = Vec::new();
341 let mut current = String::new();
342 let mut paren_depth: i32 = 0;
343
344 for ch in input.chars() {
345 match ch {
346 '(' => { paren_depth += 1; current.push(ch); }
347 ')' => { paren_depth -= 1; if paren_depth < 0 { return Err(()); } current.push(ch); }
348 ' ' if paren_depth == 0 => {
349 if !current.trim().is_empty() {
350 parts.push(current.trim().to_string());
351 current.clear();
352 }
353 }
354 _ => current.push(ch),
355 }
356 }
357 if !current.trim().is_empty() {
358 parts.push(current.trim().to_string());
359 }
360 Ok(parts)
361}
362
363#[cfg(feature = "parser")]
364pub fn parse_grid_template(input: &str) -> Result<GridTemplate, GridParseError<'_>> {
368 use crate::props::basic::pixel::parse_pixel_value;
369
370 let input = input.trim();
371
372 if input == "none" {
373 return Ok(GridTemplate::default());
374 }
375
376 let parts = split_respecting_parens(input)
377 .map_err(|()| GridParseError::InvalidValue(input))?;
378
379 let mut tracks = Vec::new();
380 for part in &parts {
381 parse_grid_track_or_repeat(part, &mut tracks)
382 .map_err(|()| GridParseError::InvalidValue(input))?;
383 }
384
385 Ok(GridTemplate {
386 tracks: GridTrackSizingVec::from_vec(tracks),
387 })
388}
389
390#[cfg(feature = "parser")]
393fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
394 const MAX_GRID_REPEAT_COUNT: usize = 10_000;
396 let input = input.trim();
397
398 if input.starts_with("repeat(") && input.ends_with(')') {
400 let content = &input[7..input.len() - 1];
401 let comma_pos = content.find(',').ok_or(())?;
403 let count_str = content[..comma_pos].trim();
404 let track_list_str = content[comma_pos + 1..].trim();
405
406 let count: usize = count_str.parse().map_err(|_| ())?;
407 if count == 0 || count > MAX_GRID_REPEAT_COUNT {
408 return Err(());
409 }
410
411 let parts = split_respecting_parens(track_list_str)?;
413 let repeat_tracks: Vec<GridTrackSizing> = parts
414 .iter()
415 .map(|p| parse_grid_track_owned(p))
416 .collect::<Result<Vec<_>, _>>()?;
417
418 for _ in 0..count {
420 tracks.extend(repeat_tracks.iter().cloned());
421 }
422 return Ok(());
423 }
424
425 tracks.push(parse_grid_track_owned(input)?);
427 Ok(())
428}
429
430#[cfg(feature = "parser")]
431fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
432 use crate::props::basic::pixel::parse_pixel_value;
433
434 let input = input.trim();
435
436 if input == "auto" {
437 return Ok(GridTrackSizing::Auto);
438 }
439
440 if input == "min-content" {
441 return Ok(GridTrackSizing::MinContent);
442 }
443
444 if input == "max-content" {
445 return Ok(GridTrackSizing::MaxContent);
446 }
447
448 if let Some(num_str) = input.strip_suffix("fr") {
449 const FR_SCALING_FACTOR: f32 = 100.0;
451 let num_str = num_str.trim();
452 if let Ok(num) = num_str.parse::<f32>() {
453 let scaled = num * FR_SCALING_FACTOR;
454 if scaled.is_nan() || scaled < crate::cast::i32_to_f32(i32::MIN) || scaled > crate::cast::i32_to_f32(i32::MAX) {
455 return Err(());
456 }
457 return Ok(GridTrackSizing::Fr(crate::cast::f32_to_i32(scaled)));
458 }
459 return Err(());
460 }
461
462 if input.starts_with("minmax(") && input.ends_with(')') {
463 let content = &input[7..input.len() - 1];
464 let parts: Vec<&str> = content.split(',').collect();
465 if parts.len() == 2 {
466 let min = parse_grid_track_owned(parts[0].trim())?;
467 let max = parse_grid_track_owned(parts[1].trim())?;
468 return Ok(GridTrackSizing::MinMax(GridMinMax {
469 min: Box::new(min),
470 max: Box::new(max),
471 }));
472 }
473 return Err(());
474 }
475
476 if input.starts_with("fit-content(") && input.ends_with(')') {
477 let size_str = &input[12..input.len() - 1].trim();
478 if let Ok(size) = parse_pixel_value(size_str) {
479 return Ok(GridTrackSizing::FitContent(size));
480 }
481 return Err(());
482 }
483
484 if let Ok(px) = parse_pixel_value(input) {
486 return Ok(GridTrackSizing::Fixed(px));
487 }
488
489 Err(())
490}
491
492#[cfg(feature = "parser")]
493pub fn parse_grid_placement(input: &str) -> Result<GridPlacement, GridParseError<'_>> {
497 let input = input.trim();
498
499 if input == "auto" {
500 return Ok(GridPlacement::default());
501 }
502
503 let parts: Vec<&str> = input.split('/').map(str::trim).collect();
505
506 let grid_start =
507 parse_grid_line_owned(parts[0]).map_err(|()| GridParseError::InvalidValue(input))?;
508 let grid_end = if parts.len() > 1 {
509 parse_grid_line_owned(parts[1]).map_err(|()| GridParseError::InvalidValue(input))?
510 } else {
511 GridLine::Auto
512 };
513
514 Ok(GridPlacement {
515 grid_start,
516 grid_end,
517 })
518}
519
520#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
524#[repr(C)]
525#[derive(Default)]
526pub enum LayoutGridAutoFlow {
527 #[default]
528 Row,
529 Column,
530 RowDense,
531 ColumnDense,
532}
533
534
535impl PrintAsCssValue for LayoutGridAutoFlow {
536 fn print_as_css_value(&self) -> alloc::string::String {
537 match self {
538 Self::Row => "row".to_string(),
539 Self::Column => "column".to_string(),
540 Self::RowDense => "row dense".to_string(),
541 Self::ColumnDense => "column dense".to_string(),
542 }
543 }
544}
545
546#[cfg(feature = "parser")]
547#[derive(Clone, PartialEq, Eq)]
548pub enum GridAutoFlowParseError<'a> {
549 InvalidValue(&'a str),
550}
551
552#[cfg(feature = "parser")]
553impl_debug_as_display!(GridAutoFlowParseError<'a>);
554#[cfg(feature = "parser")]
555impl_display! { GridAutoFlowParseError<'a>, {
556 InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
557}}
558
559#[cfg(feature = "parser")]
560#[derive(Debug, Clone, PartialEq, Eq)]
561#[repr(C, u8)]
562pub enum GridAutoFlowParseErrorOwned {
563 InvalidValue(AzString),
564}
565
566#[cfg(feature = "parser")]
567impl GridAutoFlowParseError<'_> {
568 #[must_use] pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
569 match self {
570 GridAutoFlowParseError::InvalidValue(s) => {
571 GridAutoFlowParseErrorOwned::InvalidValue((*s).to_string().into())
572 }
573 }
574 }
575}
576
577#[cfg(feature = "parser")]
578impl GridAutoFlowParseErrorOwned {
579 #[must_use] pub fn to_shared(&self) -> GridAutoFlowParseError<'_> {
580 match self {
581 Self::InvalidValue(s) => {
582 GridAutoFlowParseError::InvalidValue(s.as_str())
583 }
584 }
585 }
586}
587
588#[cfg(feature = "parser")]
589pub fn parse_layout_grid_auto_flow(
593 input: &str,
594) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'_>> {
595 match input.trim() {
596 "row" => Ok(LayoutGridAutoFlow::Row),
597 "column" => Ok(LayoutGridAutoFlow::Column),
598 "row dense" | "dense" => Ok(LayoutGridAutoFlow::RowDense),
599 "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
600 _ => Err(GridAutoFlowParseError::InvalidValue(input)),
601 }
602}
603
604#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
608#[repr(C)]
609#[derive(Default)]
610pub enum LayoutJustifySelf {
611 #[default]
612 Auto,
613 Start,
614 End,
615 Center,
616 Stretch,
617}
618
619
620impl PrintAsCssValue for LayoutJustifySelf {
621 fn print_as_css_value(&self) -> alloc::string::String {
622 match self {
623 Self::Auto => "auto".to_string(),
624 Self::Start => "start".to_string(),
625 Self::End => "end".to_string(),
626 Self::Center => "center".to_string(),
627 Self::Stretch => "stretch".to_string(),
628 }
629 }
630}
631
632#[cfg(feature = "parser")]
633#[derive(Clone, PartialEq, Eq)]
634pub enum JustifySelfParseError<'a> {
635 InvalidValue(&'a str),
636}
637
638#[cfg(feature = "parser")]
639#[derive(Debug, Clone, PartialEq, Eq)]
640#[repr(C, u8)]
641pub enum JustifySelfParseErrorOwned {
642 InvalidValue(AzString),
643}
644
645#[cfg(feature = "parser")]
646impl JustifySelfParseError<'_> {
647 #[must_use] pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
648 match self {
649 JustifySelfParseError::InvalidValue(s) => {
650 JustifySelfParseErrorOwned::InvalidValue((*s).to_string().into())
651 }
652 }
653 }
654}
655
656#[cfg(feature = "parser")]
657impl JustifySelfParseErrorOwned {
658 #[must_use] pub fn to_shared(&self) -> JustifySelfParseError<'_> {
659 match self {
660 Self::InvalidValue(s) => {
661 JustifySelfParseError::InvalidValue(s.as_str())
662 }
663 }
664 }
665}
666
667#[cfg(feature = "parser")]
668impl_debug_as_display!(JustifySelfParseError<'a>);
669#[cfg(feature = "parser")]
670impl_display! { JustifySelfParseError<'a>, {
671 InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
672}}
673
674#[cfg(feature = "parser")]
675pub fn parse_layout_justify_self(
679 input: &str,
680) -> Result<LayoutJustifySelf, JustifySelfParseError<'_>> {
681 match input.trim() {
682 "auto" => Ok(LayoutJustifySelf::Auto),
683 "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
684 "end" | "flex-end" => Ok(LayoutJustifySelf::End),
685 "center" => Ok(LayoutJustifySelf::Center),
686 "stretch" => Ok(LayoutJustifySelf::Stretch),
687 _ => Err(JustifySelfParseError::InvalidValue(input)),
688 }
689}
690
691#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
693#[repr(C)]
694#[derive(Default)]
695pub enum LayoutJustifyItems {
696 Start,
697 End,
698 Center,
699 #[default]
700 Stretch,
701}
702
703
704impl PrintAsCssValue for LayoutJustifyItems {
705 fn print_as_css_value(&self) -> alloc::string::String {
706 match self {
707 Self::Start => "start".to_string(),
708 Self::End => "end".to_string(),
709 Self::Center => "center".to_string(),
710 Self::Stretch => "stretch".to_string(),
711 }
712 }
713}
714
715#[cfg(feature = "parser")]
716#[derive(Clone, PartialEq, Eq)]
717pub enum JustifyItemsParseError<'a> {
718 InvalidValue(&'a str),
719}
720
721#[cfg(feature = "parser")]
722#[derive(Debug, Clone, PartialEq, Eq)]
723#[repr(C, u8)]
724pub enum JustifyItemsParseErrorOwned {
725 InvalidValue(AzString),
726}
727
728#[cfg(feature = "parser")]
729impl JustifyItemsParseError<'_> {
730 #[must_use] pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
731 match self {
732 JustifyItemsParseError::InvalidValue(s) => {
733 JustifyItemsParseErrorOwned::InvalidValue((*s).to_string().into())
734 }
735 }
736 }
737}
738
739#[cfg(feature = "parser")]
740impl JustifyItemsParseErrorOwned {
741 #[must_use] pub fn to_shared(&self) -> JustifyItemsParseError<'_> {
742 match self {
743 Self::InvalidValue(s) => {
744 JustifyItemsParseError::InvalidValue(s.as_str())
745 }
746 }
747 }
748}
749
750#[cfg(feature = "parser")]
751impl_debug_as_display!(JustifyItemsParseError<'a>);
752#[cfg(feature = "parser")]
753impl_display! { JustifyItemsParseError<'a>, {
754 InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
755}}
756
757#[cfg(feature = "parser")]
758pub fn parse_layout_justify_items(
762 input: &str,
763) -> Result<LayoutJustifyItems, JustifyItemsParseError<'_>> {
764 match input.trim() {
765 "start" => Ok(LayoutJustifyItems::Start),
766 "end" => Ok(LayoutJustifyItems::End),
767 "center" => Ok(LayoutJustifyItems::Center),
768 "stretch" => Ok(LayoutJustifyItems::Stretch),
769 _ => Err(JustifyItemsParseError::InvalidValue(input)),
770 }
771}
772
773#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
776#[repr(C)]
777pub struct LayoutGap {
778 pub inner: PixelValue,
779}
780
781impl core::fmt::Debug for LayoutGap {
782 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
783 write!(f, "{}", self.inner)
784 }
785}
786
787impl PrintAsCssValue for LayoutGap {
788 fn print_as_css_value(&self) -> alloc::string::String {
789 self.inner.print_as_css_value()
790 }
791}
792
793impl FormatAsRustCode for LayoutGridAutoFlow {
796 fn format_as_rust_code(&self, _tabs: usize) -> String {
797 format!(
798 "LayoutGridAutoFlow::{}",
799 match self {
800 Self::Row => "Row",
801 Self::Column => "Column",
802 Self::RowDense => "RowDense",
803 Self::ColumnDense => "ColumnDense",
804 }
805 )
806 }
807}
808
809impl FormatAsRustCode for LayoutJustifySelf {
810 fn format_as_rust_code(&self, _tabs: usize) -> String {
811 format!(
812 "LayoutJustifySelf::{}",
813 match self {
814 Self::Auto => "Auto",
815 Self::Start => "Start",
816 Self::End => "End",
817 Self::Center => "Center",
818 Self::Stretch => "Stretch",
819 }
820 )
821 }
822}
823
824impl FormatAsRustCode for LayoutJustifyItems {
825 fn format_as_rust_code(&self, _tabs: usize) -> String {
826 format!(
827 "LayoutJustifyItems::{}",
828 match self {
829 Self::Start => "Start",
830 Self::End => "End",
831 Self::Center => "Center",
832 Self::Stretch => "Stretch",
833 }
834 )
835 }
836}
837
838impl FormatAsRustCode for LayoutGap {
839 fn format_as_rust_code(&self, _tabs: usize) -> String {
840 use crate::codegen::format::format_pixel_value;
841 format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
842 }
843}
844
845impl FormatAsRustCode for GridTrackSizing {
846 #[allow(clippy::only_used_in_recursion)]
849 fn format_as_rust_code(&self, tabs: usize) -> String {
850 use crate::codegen::format::format_pixel_value;
851 match self {
852 Self::Fixed(pv) => {
853 format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
854 }
855 Self::Fr(f) => format!("GridTrackSizing::Fr({f})"),
856 Self::MinContent => "GridTrackSizing::MinContent".to_string(),
857 Self::MaxContent => "GridTrackSizing::MaxContent".to_string(),
858 Self::Auto => "GridTrackSizing::Auto".to_string(),
859 Self::MinMax(minmax) => {
860 format!(
861 "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
862 minmax.min.format_as_rust_code(tabs),
863 minmax.max.format_as_rust_code(tabs)
864 )
865 }
866 Self::FitContent(pv) => {
867 format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
868 }
869 }
870 }
871}
872
873impl FormatAsRustCode for GridAutoTracks {
874 fn format_as_rust_code(&self, tabs: usize) -> String {
875 let tracks: Vec<String> = self
876 .tracks
877 .as_ref()
878 .iter()
879 .map(|t| t.format_as_rust_code(tabs))
880 .collect();
881 format!(
882 "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
883 tracks.join(", ")
884 )
885 }
886}
887
888impl FormatAsRustCode for GridTemplateAreas {
889 fn format_as_rust_code(&self, _tabs: usize) -> String {
890 format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
891 }
892}
893
894#[cfg(feature = "parser")]
895pub fn parse_layout_gap(
899 input: &str,
900) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'_>> {
901 crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
902}
903
904#[cfg(feature = "parser")]
905pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
909 let input = input.trim();
910
911 if input == "auto" {
912 return Ok(GridLine::Auto);
913 }
914
915 if let Some(num_str) = input.strip_prefix("span ") {
916 let num_str = num_str.trim();
917 if let Ok(num) = num_str.parse::<i32>() {
918 return Ok(GridLine::Span(num));
919 }
920 return Err(());
921 }
922
923 if let Ok(num) = input.parse::<i32>() {
925 return Ok(GridLine::Line(num));
926 }
927
928 Ok(GridLine::Named(NamedGridLine::create(
930 input.to_string().into(),
931 None,
932 )))
933}
934
935#[cfg(all(test, feature = "parser"))]
936mod tests {
937 use super::*;
938
939 #[test]
941 fn test_parse_grid_template_none() {
942 let result = parse_grid_template("none").unwrap();
943 assert_eq!(result.tracks.len(), 0);
944 }
945
946 #[test]
947 fn test_parse_grid_template_single_px() {
948 let result = parse_grid_template("100px").unwrap();
949 assert_eq!(result.tracks.len(), 1);
950 assert!(matches!(
951 result.tracks.as_ref()[0],
952 GridTrackSizing::Fixed(_)
953 ));
954 }
955
956 #[test]
957 fn test_parse_grid_template_multiple_tracks() {
958 let result = parse_grid_template("100px 200px 1fr").unwrap();
959 assert_eq!(result.tracks.len(), 3);
960 }
961
962 #[test]
963 fn test_parse_grid_template_fr_units() {
964 let result = parse_grid_template("1fr 2fr 1fr").unwrap();
965 assert_eq!(result.tracks.len(), 3);
966 assert!(matches!(
967 result.tracks.as_ref()[0],
968 GridTrackSizing::Fr(100)
969 ));
970 assert!(matches!(
971 result.tracks.as_ref()[1],
972 GridTrackSizing::Fr(200)
973 ));
974 }
975
976 #[test]
977 fn test_parse_grid_template_fractional_fr() {
978 let result = parse_grid_template("0.5fr 1.5fr").unwrap();
979 assert_eq!(result.tracks.len(), 2);
980 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
981 assert!(matches!(
982 result.tracks.as_ref()[1],
983 GridTrackSizing::Fr(150)
984 ));
985 }
986
987 #[test]
988 fn test_parse_grid_template_auto() {
989 let result = parse_grid_template("auto 100px auto").unwrap();
990 assert_eq!(result.tracks.len(), 3);
991 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
992 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
993 }
994
995 #[test]
996 fn test_parse_grid_template_min_max_content() {
997 let result = parse_grid_template("min-content max-content auto").unwrap();
998 assert_eq!(result.tracks.len(), 3);
999 assert!(matches!(
1000 result.tracks.as_ref()[0],
1001 GridTrackSizing::MinContent
1002 ));
1003 assert!(matches!(
1004 result.tracks.as_ref()[1],
1005 GridTrackSizing::MaxContent
1006 ));
1007 }
1008
1009 #[test]
1010 fn test_parse_grid_template_minmax() {
1011 let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
1012 assert_eq!(result.tracks.len(), 1);
1013 assert!(matches!(
1014 result.tracks.as_ref()[0],
1015 GridTrackSizing::MinMax(_)
1016 ));
1017 }
1018
1019 #[test]
1020 fn test_parse_grid_template_minmax_complex() {
1021 let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
1022 assert_eq!(result.tracks.len(), 1);
1023 }
1024
1025 #[test]
1026 fn test_parse_grid_template_fit_content() {
1027 let result = parse_grid_template("fit-content(200px)").unwrap();
1028 assert_eq!(result.tracks.len(), 1);
1029 assert!(matches!(
1030 result.tracks.as_ref()[0],
1031 GridTrackSizing::FitContent(_)
1032 ));
1033 }
1034
1035 #[test]
1036 fn test_parse_grid_template_mixed() {
1037 let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1038 assert_eq!(result.tracks.len(), 4);
1039 }
1040
1041 #[test]
1042 fn test_parse_grid_template_percent() {
1043 let result = parse_grid_template("25% 50% 25%").unwrap();
1044 assert_eq!(result.tracks.len(), 3);
1045 }
1046
1047 #[test]
1048 fn test_parse_grid_template_em_units() {
1049 let result = parse_grid_template("10em 20em 1fr").unwrap();
1050 assert_eq!(result.tracks.len(), 3);
1051 }
1052
1053 #[test]
1055 fn test_parse_grid_placement_auto() {
1056 let result = parse_grid_placement("auto").unwrap();
1057 assert!(matches!(result.grid_start, GridLine::Auto));
1058 assert!(matches!(result.grid_end, GridLine::Auto));
1059 }
1060
1061 #[test]
1062 fn test_parse_grid_placement_line_number() {
1063 let result = parse_grid_placement("1").unwrap();
1064 assert!(matches!(result.grid_start, GridLine::Line(1)));
1065 assert!(matches!(result.grid_end, GridLine::Auto));
1066 }
1067
1068 #[test]
1069 fn test_parse_grid_placement_negative_line() {
1070 let result = parse_grid_placement("-1").unwrap();
1071 assert!(matches!(result.grid_start, GridLine::Line(-1)));
1072 }
1073
1074 #[test]
1075 fn test_parse_grid_placement_span() {
1076 let result = parse_grid_placement("span 2").unwrap();
1077 assert!(matches!(result.grid_start, GridLine::Span(2)));
1078 }
1079
1080 #[test]
1081 fn test_parse_grid_placement_start_end() {
1082 let result = parse_grid_placement("1 / 3").unwrap();
1083 assert!(matches!(result.grid_start, GridLine::Line(1)));
1084 assert!(matches!(result.grid_end, GridLine::Line(3)));
1085 }
1086
1087 #[test]
1088 fn test_parse_grid_placement_span_end() {
1089 let result = parse_grid_placement("1 / span 2").unwrap();
1090 assert!(matches!(result.grid_start, GridLine::Line(1)));
1091 assert!(matches!(result.grid_end, GridLine::Span(2)));
1092 }
1093
1094 #[test]
1095 fn test_parse_grid_placement_named_line() {
1096 let result = parse_grid_placement("header-start").unwrap();
1097 assert!(matches!(result.grid_start, GridLine::Named(_)));
1098 }
1099
1100 #[test]
1101 fn test_parse_grid_placement_named_start_end() {
1102 let result = parse_grid_placement("header-start / header-end").unwrap();
1103 assert!(matches!(result.grid_start, GridLine::Named(_)));
1104 assert!(matches!(result.grid_end, GridLine::Named(_)));
1105 }
1106
1107 #[test]
1109 fn test_parse_grid_template_whitespace() {
1110 let result = parse_grid_template(" 100px 200px ").unwrap();
1111 assert_eq!(result.tracks.len(), 2);
1112 }
1113
1114 #[test]
1115 fn test_parse_grid_placement_whitespace() {
1116 let result = parse_grid_placement(" 1 / 3 ").unwrap();
1117 assert!(matches!(result.grid_start, GridLine::Line(1)));
1118 assert!(matches!(result.grid_end, GridLine::Line(3)));
1119 }
1120
1121 #[test]
1122 fn test_parse_grid_template_zero_fr() {
1123 let result = parse_grid_template("0fr").unwrap();
1124 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1125 }
1126
1127 #[test]
1128 fn test_parse_grid_placement_zero_line() {
1129 let result = parse_grid_placement("0").unwrap();
1130 assert!(matches!(result.grid_start, GridLine::Line(0)));
1131 }
1132
1133 #[test]
1135 fn test_parse_grid_template_repeat_fr() {
1136 let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1137 assert_eq!(result.tracks.len(), 3);
1138 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
1139 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1140 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1141 }
1142
1143 #[test]
1144 fn test_parse_grid_template_repeat_px() {
1145 let result = parse_grid_template("repeat(2, 100px)").unwrap();
1146 assert_eq!(result.tracks.len(), 2);
1147 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1148 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
1149 }
1150
1151 #[test]
1152 fn test_parse_grid_template_repeat_multiple_tracks() {
1153 let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1155 assert_eq!(result.tracks.len(), 4);
1156 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1157 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1158 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
1159 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
1160 }
1161
1162 #[test]
1163 fn test_parse_grid_template_repeat_with_other_tracks() {
1164 let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1166 assert_eq!(result.tracks.len(), 4);
1167 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1168 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1169 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1170 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1171 }
1172
1173 #[test]
1174 fn test_parse_grid_template_repeat_minmax() {
1175 let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1176 assert_eq!(result.tracks.len(), 3);
1177 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
1178 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
1179 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
1180 }
1181}
1182
1183#[repr(C)]
1188#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1189pub struct GridAreaDefinition {
1190 pub name: AzString,
1191 pub row_start: u16,
1192 pub row_end: u16,
1193 pub column_start: u16,
1194 pub column_end: u16,
1195}
1196
1197impl_option!(
1198 GridAreaDefinition,
1199 OptionGridAreaDefinition,
1200 copy = false,
1201 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1202);
1203
1204impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
1205impl_vec_clone!(
1206 GridAreaDefinition,
1207 GridAreaDefinitionVec,
1208 GridAreaDefinitionVecDestructor
1209);
1210impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1211impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1212impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1213impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1214impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1215impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1216impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1217
1218#[repr(C)]
1228#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1229pub struct GridTemplateAreas {
1230 pub areas: GridAreaDefinitionVec,
1231}
1232
1233impl Default for GridTemplateAreas {
1234 fn default() -> Self {
1235 Self { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
1236 }
1237}
1238
1239impl PrintAsCssValue for GridTemplateAreas {
1240 fn print_as_css_value(&self) -> String {
1241 let areas_slice = self.areas.as_ref();
1242 if areas_slice.is_empty() {
1243 return "none".to_string();
1244 }
1245 let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1247 let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1248 let num_rows = (max_row - 1) as usize;
1249 let num_cols = (max_col - 1) as usize;
1250 let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1251 for area in areas_slice {
1252 let row_start = area.row_start as usize - 1;
1253 let row_end = area.row_end as usize - 1;
1254 let col_start = area.column_start as usize - 1;
1255 let col_end = area.column_end as usize - 1;
1256 for row in grid.iter_mut().take(row_end).skip(row_start) {
1257 for cell in row.iter_mut().take(col_end).skip(col_start) {
1258 *cell = area.name.as_str().to_string();
1259 }
1260 }
1261 }
1262 grid.iter()
1263 .map(|row| format!("\"{}\"", row.join(" ")))
1264 .collect::<Vec<_>>()
1265 .join(" ")
1266 }
1267}
1268
1269#[cfg(feature = "parser")]
1277pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1281 use alloc::collections::BTreeMap;
1282 let input = input.trim();
1283 if input == "none" {
1284 return Ok(GridTemplateAreas::default());
1285 }
1286
1287 let mut rows: Vec<Vec<String>> = Vec::new();
1289 let mut i = 0;
1290 let bytes = input.as_bytes();
1291 while i < bytes.len() {
1292 if bytes[i] == b'"' || bytes[i] == b'\'' {
1293 let quote = bytes[i];
1294 i += 1;
1295 let start = i;
1296 while i < bytes.len() && bytes[i] != quote {
1297 i += 1;
1298 }
1299 if i >= bytes.len() {
1300 return Err(());
1301 }
1302 let row_str = &input[start..i];
1303 let cells: Vec<String> = row_str.split_whitespace().map(std::string::ToString::to_string).collect();
1304 if cells.is_empty() {
1305 return Err(());
1306 }
1307 rows.push(cells);
1308 }
1309 i += 1;
1311 }
1312
1313 if rows.is_empty() {
1314 return Err(());
1315 }
1316
1317 let num_cols = rows[0].len();
1319 for row in &rows {
1320 if row.len() != num_cols {
1321 return Err(());
1322 }
1323 }
1324
1325 let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1327
1328 for (row_idx, row) in rows.iter().enumerate() {
1329 for (col_idx, cell) in row.iter().enumerate() {
1330 if cell == "." {
1331 continue; }
1333 let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
1334 entry.0 = entry.0.min(row_idx);
1335 entry.1 = entry.1.max(row_idx);
1336 entry.2 = entry.2.min(col_idx);
1337 entry.3 = entry.3.max(col_idx);
1338 }
1339 }
1340
1341 let mut areas = Vec::new();
1343 for (name, (min_row, max_row, min_col, max_col)) in area_map {
1344 areas.push(GridAreaDefinition {
1345 name: name.into(),
1346 row_start: u16::try_from(min_row + 1).unwrap_or(u16::MAX),
1347 row_end: u16::try_from(max_row + 2).unwrap_or(u16::MAX), column_start: u16::try_from(min_col + 1).unwrap_or(u16::MAX),
1349 column_end: u16::try_from(max_col + 2).unwrap_or(u16::MAX),
1350 });
1351 }
1352
1353 Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
1354}