1use 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#[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 [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 GridTrackSizing::Fixed(px) => px.print_as_css_value(),
79 GridTrackSizing::Fr(f) => format!("{}fr", f),
80 GridTrackSizing::MinContent => "min-content".to_string(),
81 GridTrackSizing::MaxContent => "max-content".to_string(),
82 GridTrackSizing::Auto => "auto".to_string(),
83 GridTrackSizing::MinMax(minmax) => {
84 format!(
85 "minmax({}, {})",
86 minmax.min.print_as_css_value(),
87 minmax.max.print_as_css_value()
88 )
89 }
90 GridTrackSizing::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 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#[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#[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 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#[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 GridLine::Auto => "auto".to_string(),
250 GridLine::Line(n) => n.to_string(),
251 GridLine::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 GridLine::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 GridPlacement {
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)]
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)]
315#[repr(C, u8)]
316pub enum GridParseErrorOwned {
317 InvalidValue(AzString),
318}
319
320#[cfg(feature = "parser")]
321impl<'a> GridParseError<'a> {
322 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 pub fn to_shared<'a>(&'a self) -> GridParseError<'a> {
332 match self {
333 GridParseErrorOwned::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<'a>(input: &'a str) -> Result<GridTemplate, GridParseError<'a>> {
365 use crate::props::basic::pixel::parse_pixel_value;
366
367 let input = input.trim();
368
369 if input == "none" {
370 return Ok(GridTemplate::default());
371 }
372
373 let parts = split_respecting_parens(input)
374 .map_err(|_| GridParseError::InvalidValue(input))?;
375
376 let mut tracks = Vec::new();
377 for part in &parts {
378 parse_grid_track_or_repeat(part, &mut tracks)
379 .map_err(|_| GridParseError::InvalidValue(input))?;
380 }
381
382 Ok(GridTemplate {
383 tracks: GridTrackSizingVec::from_vec(tracks),
384 })
385}
386
387#[cfg(feature = "parser")]
390fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
391 let input = input.trim();
392
393 if input.starts_with("repeat(") && input.ends_with(')') {
395 let content = &input[7..input.len() - 1];
396 let comma_pos = content.find(',').ok_or(())?;
398 let count_str = content[..comma_pos].trim();
399 let track_list_str = content[comma_pos + 1..].trim();
400
401 let count: usize = count_str.parse().map_err(|_| ())?;
402 const MAX_GRID_REPEAT_COUNT: usize = 10_000;
403 if count == 0 || count > MAX_GRID_REPEAT_COUNT {
404 return Err(());
405 }
406
407 let parts = split_respecting_parens(track_list_str)?;
409 let repeat_tracks: Vec<GridTrackSizing> = parts
410 .iter()
411 .map(|p| parse_grid_track_owned(p))
412 .collect::<Result<Vec<_>, _>>()?;
413
414 for _ in 0..count {
416 tracks.extend(repeat_tracks.iter().cloned());
417 }
418 return Ok(());
419 }
420
421 tracks.push(parse_grid_track_owned(input)?);
423 Ok(())
424}
425
426#[cfg(feature = "parser")]
427fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
428 use crate::props::basic::pixel::parse_pixel_value;
429
430 let input = input.trim();
431
432 if input == "auto" {
433 return Ok(GridTrackSizing::Auto);
434 }
435
436 if input == "min-content" {
437 return Ok(GridTrackSizing::MinContent);
438 }
439
440 if input == "max-content" {
441 return Ok(GridTrackSizing::MaxContent);
442 }
443
444 if input.ends_with("fr") {
445 const FR_SCALING_FACTOR: f32 = 100.0;
447 let num_str = &input[..input.len() - 2].trim();
448 if let Ok(num) = num_str.parse::<f32>() {
449 let scaled = num * FR_SCALING_FACTOR;
450 if scaled.is_nan() || scaled < (i32::MIN as f32) || scaled > (i32::MAX as f32) {
451 return Err(());
452 }
453 return Ok(GridTrackSizing::Fr(scaled as i32));
454 }
455 return Err(());
456 }
457
458 if input.starts_with("minmax(") && input.ends_with(')') {
459 let content = &input[7..input.len() - 1];
460 let parts: Vec<&str> = content.split(',').collect();
461 if parts.len() == 2 {
462 let min = parse_grid_track_owned(parts[0].trim())?;
463 let max = parse_grid_track_owned(parts[1].trim())?;
464 return Ok(GridTrackSizing::MinMax(GridMinMax {
465 min: Box::new(min),
466 max: Box::new(max),
467 }));
468 }
469 return Err(());
470 }
471
472 if input.starts_with("fit-content(") && input.ends_with(')') {
473 let size_str = &input[12..input.len() - 1].trim();
474 if let Ok(size) = parse_pixel_value(size_str) {
475 return Ok(GridTrackSizing::FitContent(size));
476 }
477 return Err(());
478 }
479
480 if let Ok(px) = parse_pixel_value(input) {
482 return Ok(GridTrackSizing::Fixed(px));
483 }
484
485 Err(())
486}
487
488#[cfg(feature = "parser")]
489pub fn parse_grid_placement<'a>(input: &'a str) -> Result<GridPlacement, GridParseError<'a>> {
490 let input = input.trim();
491
492 if input == "auto" {
493 return Ok(GridPlacement::default());
494 }
495
496 let parts: Vec<&str> = input.split('/').map(|s| s.trim()).collect();
498
499 let grid_start =
500 parse_grid_line_owned(parts[0]).map_err(|_| GridParseError::InvalidValue(input))?;
501 let grid_end = if parts.len() > 1 {
502 parse_grid_line_owned(parts[1]).map_err(|_| GridParseError::InvalidValue(input))?
503 } else {
504 GridLine::Auto
505 };
506
507 Ok(GridPlacement {
508 grid_start,
509 grid_end,
510 })
511}
512
513#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
517#[repr(C)]
518#[derive(Default)]
519pub enum LayoutGridAutoFlow {
520 #[default]
521 Row,
522 Column,
523 RowDense,
524 ColumnDense,
525}
526
527
528impl crate::props::formatter::PrintAsCssValue for LayoutGridAutoFlow {
529 fn print_as_css_value(&self) -> alloc::string::String {
530 match self {
531 LayoutGridAutoFlow::Row => "row".to_string(),
532 LayoutGridAutoFlow::Column => "column".to_string(),
533 LayoutGridAutoFlow::RowDense => "row dense".to_string(),
534 LayoutGridAutoFlow::ColumnDense => "column dense".to_string(),
535 }
536 }
537}
538
539#[cfg(feature = "parser")]
540#[derive(Clone, PartialEq)]
541pub enum GridAutoFlowParseError<'a> {
542 InvalidValue(&'a str),
543}
544
545#[cfg(feature = "parser")]
546impl_debug_as_display!(GridAutoFlowParseError<'a>);
547#[cfg(feature = "parser")]
548impl_display! { GridAutoFlowParseError<'a>, {
549 InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
550}}
551
552#[cfg(feature = "parser")]
553#[derive(Debug, Clone, PartialEq)]
554#[repr(C, u8)]
555pub enum GridAutoFlowParseErrorOwned {
556 InvalidValue(AzString),
557}
558
559#[cfg(feature = "parser")]
560impl<'a> GridAutoFlowParseError<'a> {
561 pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
562 match self {
563 GridAutoFlowParseError::InvalidValue(s) => {
564 GridAutoFlowParseErrorOwned::InvalidValue(s.to_string().into())
565 }
566 }
567 }
568}
569
570#[cfg(feature = "parser")]
571impl GridAutoFlowParseErrorOwned {
572 pub fn to_shared<'a>(&'a self) -> GridAutoFlowParseError<'a> {
573 match self {
574 GridAutoFlowParseErrorOwned::InvalidValue(s) => {
575 GridAutoFlowParseError::InvalidValue(s.as_str())
576 }
577 }
578 }
579}
580
581#[cfg(feature = "parser")]
582pub fn parse_layout_grid_auto_flow<'a>(
583 input: &'a str,
584) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'a>> {
585 match input.trim() {
586 "row" => Ok(LayoutGridAutoFlow::Row),
587 "column" => Ok(LayoutGridAutoFlow::Column),
588 "row dense" => Ok(LayoutGridAutoFlow::RowDense),
589 "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
590 "dense" => Ok(LayoutGridAutoFlow::RowDense),
591 _ => Err(GridAutoFlowParseError::InvalidValue(input)),
592 }
593}
594
595#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
599#[repr(C)]
600#[derive(Default)]
601pub enum LayoutJustifySelf {
602 #[default]
603 Auto,
604 Start,
605 End,
606 Center,
607 Stretch,
608}
609
610
611impl crate::props::formatter::PrintAsCssValue for LayoutJustifySelf {
612 fn print_as_css_value(&self) -> alloc::string::String {
613 match self {
614 LayoutJustifySelf::Auto => "auto".to_string(),
615 LayoutJustifySelf::Start => "start".to_string(),
616 LayoutJustifySelf::End => "end".to_string(),
617 LayoutJustifySelf::Center => "center".to_string(),
618 LayoutJustifySelf::Stretch => "stretch".to_string(),
619 }
620 }
621}
622
623#[cfg(feature = "parser")]
624#[derive(Clone, PartialEq)]
625pub enum JustifySelfParseError<'a> {
626 InvalidValue(&'a str),
627}
628
629#[cfg(feature = "parser")]
630#[derive(Debug, Clone, PartialEq)]
631#[repr(C, u8)]
632pub enum JustifySelfParseErrorOwned {
633 InvalidValue(AzString),
634}
635
636#[cfg(feature = "parser")]
637impl<'a> JustifySelfParseError<'a> {
638 pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
639 match self {
640 JustifySelfParseError::InvalidValue(s) => {
641 JustifySelfParseErrorOwned::InvalidValue(s.to_string().into())
642 }
643 }
644 }
645}
646
647#[cfg(feature = "parser")]
648impl JustifySelfParseErrorOwned {
649 pub fn to_shared<'a>(&'a self) -> JustifySelfParseError<'a> {
650 match self {
651 JustifySelfParseErrorOwned::InvalidValue(s) => {
652 JustifySelfParseError::InvalidValue(s.as_str())
653 }
654 }
655 }
656}
657
658#[cfg(feature = "parser")]
659impl_debug_as_display!(JustifySelfParseError<'a>);
660#[cfg(feature = "parser")]
661impl_display! { JustifySelfParseError<'a>, {
662 InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
663}}
664
665#[cfg(feature = "parser")]
666pub fn parse_layout_justify_self<'a>(
667 input: &'a str,
668) -> Result<LayoutJustifySelf, JustifySelfParseError<'a>> {
669 match input.trim() {
670 "auto" => Ok(LayoutJustifySelf::Auto),
671 "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
672 "end" | "flex-end" => Ok(LayoutJustifySelf::End),
673 "center" => Ok(LayoutJustifySelf::Center),
674 "stretch" => Ok(LayoutJustifySelf::Stretch),
675 _ => Err(JustifySelfParseError::InvalidValue(input)),
676 }
677}
678
679#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
681#[repr(C)]
682#[derive(Default)]
683pub enum LayoutJustifyItems {
684 Start,
685 End,
686 Center,
687 #[default]
688 Stretch,
689}
690
691
692impl crate::props::formatter::PrintAsCssValue for LayoutJustifyItems {
693 fn print_as_css_value(&self) -> alloc::string::String {
694 match self {
695 LayoutJustifyItems::Start => "start".to_string(),
696 LayoutJustifyItems::End => "end".to_string(),
697 LayoutJustifyItems::Center => "center".to_string(),
698 LayoutJustifyItems::Stretch => "stretch".to_string(),
699 }
700 }
701}
702
703#[cfg(feature = "parser")]
704#[derive(Clone, PartialEq)]
705pub enum JustifyItemsParseError<'a> {
706 InvalidValue(&'a str),
707}
708
709#[cfg(feature = "parser")]
710#[derive(Debug, Clone, PartialEq)]
711#[repr(C, u8)]
712pub enum JustifyItemsParseErrorOwned {
713 InvalidValue(AzString),
714}
715
716#[cfg(feature = "parser")]
717impl<'a> JustifyItemsParseError<'a> {
718 pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
719 match self {
720 JustifyItemsParseError::InvalidValue(s) => {
721 JustifyItemsParseErrorOwned::InvalidValue(s.to_string().into())
722 }
723 }
724 }
725}
726
727#[cfg(feature = "parser")]
728impl JustifyItemsParseErrorOwned {
729 pub fn to_shared<'a>(&'a self) -> JustifyItemsParseError<'a> {
730 match self {
731 JustifyItemsParseErrorOwned::InvalidValue(s) => {
732 JustifyItemsParseError::InvalidValue(s.as_str())
733 }
734 }
735 }
736}
737
738#[cfg(feature = "parser")]
739impl_debug_as_display!(JustifyItemsParseError<'a>);
740#[cfg(feature = "parser")]
741impl_display! { JustifyItemsParseError<'a>, {
742 InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
743}}
744
745#[cfg(feature = "parser")]
746pub fn parse_layout_justify_items<'a>(
747 input: &'a str,
748) -> Result<LayoutJustifyItems, JustifyItemsParseError<'a>> {
749 match input.trim() {
750 "start" => Ok(LayoutJustifyItems::Start),
751 "end" => Ok(LayoutJustifyItems::End),
752 "center" => Ok(LayoutJustifyItems::Center),
753 "stretch" => Ok(LayoutJustifyItems::Stretch),
754 _ => Err(JustifyItemsParseError::InvalidValue(input)),
755 }
756}
757
758#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
761#[repr(C)]
762pub struct LayoutGap {
763 pub inner: crate::props::basic::pixel::PixelValue,
764}
765
766impl core::fmt::Debug for LayoutGap {
767 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
768 write!(f, "{}", self.inner)
769 }
770}
771
772impl crate::props::formatter::PrintAsCssValue for LayoutGap {
773 fn print_as_css_value(&self) -> alloc::string::String {
774 self.inner.print_as_css_value()
775 }
776}
777
778impl FormatAsRustCode for LayoutGridAutoFlow {
781 fn format_as_rust_code(&self, _tabs: usize) -> String {
782 format!(
783 "LayoutGridAutoFlow::{}",
784 match self {
785 LayoutGridAutoFlow::Row => "Row",
786 LayoutGridAutoFlow::Column => "Column",
787 LayoutGridAutoFlow::RowDense => "RowDense",
788 LayoutGridAutoFlow::ColumnDense => "ColumnDense",
789 }
790 )
791 }
792}
793
794impl FormatAsRustCode for LayoutJustifySelf {
795 fn format_as_rust_code(&self, _tabs: usize) -> String {
796 format!(
797 "LayoutJustifySelf::{}",
798 match self {
799 LayoutJustifySelf::Auto => "Auto",
800 LayoutJustifySelf::Start => "Start",
801 LayoutJustifySelf::End => "End",
802 LayoutJustifySelf::Center => "Center",
803 LayoutJustifySelf::Stretch => "Stretch",
804 }
805 )
806 }
807}
808
809impl FormatAsRustCode for LayoutJustifyItems {
810 fn format_as_rust_code(&self, _tabs: usize) -> String {
811 format!(
812 "LayoutJustifyItems::{}",
813 match self {
814 LayoutJustifyItems::Start => "Start",
815 LayoutJustifyItems::End => "End",
816 LayoutJustifyItems::Center => "Center",
817 LayoutJustifyItems::Stretch => "Stretch",
818 }
819 )
820 }
821}
822
823impl FormatAsRustCode for LayoutGap {
824 fn format_as_rust_code(&self, _tabs: usize) -> String {
825 use crate::format_rust_code::format_pixel_value;
826 format!("LayoutGap {{ inner: {} }}", format_pixel_value(&self.inner))
827 }
828}
829
830impl FormatAsRustCode for GridTrackSizing {
831 fn format_as_rust_code(&self, tabs: usize) -> String {
832 use crate::format_rust_code::format_pixel_value;
833 match self {
834 GridTrackSizing::Fixed(pv) => {
835 format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
836 }
837 GridTrackSizing::Fr(f) => format!("GridTrackSizing::Fr({})", f),
838 GridTrackSizing::MinContent => "GridTrackSizing::MinContent".to_string(),
839 GridTrackSizing::MaxContent => "GridTrackSizing::MaxContent".to_string(),
840 GridTrackSizing::Auto => "GridTrackSizing::Auto".to_string(),
841 GridTrackSizing::MinMax(minmax) => {
842 format!(
843 "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
844 minmax.min.format_as_rust_code(tabs),
845 minmax.max.format_as_rust_code(tabs)
846 )
847 }
848 GridTrackSizing::FitContent(pv) => {
849 format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
850 }
851 }
852 }
853}
854
855impl FormatAsRustCode for GridAutoTracks {
856 fn format_as_rust_code(&self, tabs: usize) -> String {
857 let tracks: Vec<String> = self
858 .tracks
859 .as_ref()
860 .iter()
861 .map(|t| t.format_as_rust_code(tabs))
862 .collect();
863 format!(
864 "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
865 tracks.join(", ")
866 )
867 }
868}
869
870impl FormatAsRustCode for GridTemplateAreas {
871 fn format_as_rust_code(&self, _tabs: usize) -> String {
872 format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
873 }
874}
875
876#[cfg(feature = "parser")]
877pub fn parse_layout_gap<'a>(
878 input: &'a str,
879) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'a>> {
880 crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
881}
882
883#[cfg(feature = "parser")]
884pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
885 let input = input.trim();
886
887 if input == "auto" {
888 return Ok(GridLine::Auto);
889 }
890
891 if input.starts_with("span ") {
892 let num_str = &input[5..].trim();
893 if let Ok(num) = num_str.parse::<i32>() {
894 return Ok(GridLine::Span(num));
895 }
896 return Err(());
897 }
898
899 if let Ok(num) = input.parse::<i32>() {
901 return Ok(GridLine::Line(num));
902 }
903
904 Ok(GridLine::Named(NamedGridLine::create(
906 input.to_string().into(),
907 None,
908 )))
909}
910
911#[cfg(all(test, feature = "parser"))]
912mod tests {
913 use super::*;
914
915 #[test]
917 fn test_parse_grid_template_none() {
918 let result = parse_grid_template("none").unwrap();
919 assert_eq!(result.tracks.len(), 0);
920 }
921
922 #[test]
923 fn test_parse_grid_template_single_px() {
924 let result = parse_grid_template("100px").unwrap();
925 assert_eq!(result.tracks.len(), 1);
926 assert!(matches!(
927 result.tracks.as_ref()[0],
928 GridTrackSizing::Fixed(_)
929 ));
930 }
931
932 #[test]
933 fn test_parse_grid_template_multiple_tracks() {
934 let result = parse_grid_template("100px 200px 1fr").unwrap();
935 assert_eq!(result.tracks.len(), 3);
936 }
937
938 #[test]
939 fn test_parse_grid_template_fr_units() {
940 let result = parse_grid_template("1fr 2fr 1fr").unwrap();
941 assert_eq!(result.tracks.len(), 3);
942 assert!(matches!(
943 result.tracks.as_ref()[0],
944 GridTrackSizing::Fr(100)
945 ));
946 assert!(matches!(
947 result.tracks.as_ref()[1],
948 GridTrackSizing::Fr(200)
949 ));
950 }
951
952 #[test]
953 fn test_parse_grid_template_fractional_fr() {
954 let result = parse_grid_template("0.5fr 1.5fr").unwrap();
955 assert_eq!(result.tracks.len(), 2);
956 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
957 assert!(matches!(
958 result.tracks.as_ref()[1],
959 GridTrackSizing::Fr(150)
960 ));
961 }
962
963 #[test]
964 fn test_parse_grid_template_auto() {
965 let result = parse_grid_template("auto 100px auto").unwrap();
966 assert_eq!(result.tracks.len(), 3);
967 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
968 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
969 }
970
971 #[test]
972 fn test_parse_grid_template_min_max_content() {
973 let result = parse_grid_template("min-content max-content auto").unwrap();
974 assert_eq!(result.tracks.len(), 3);
975 assert!(matches!(
976 result.tracks.as_ref()[0],
977 GridTrackSizing::MinContent
978 ));
979 assert!(matches!(
980 result.tracks.as_ref()[1],
981 GridTrackSizing::MaxContent
982 ));
983 }
984
985 #[test]
986 fn test_parse_grid_template_minmax() {
987 let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
988 assert_eq!(result.tracks.len(), 1);
989 assert!(matches!(
990 result.tracks.as_ref()[0],
991 GridTrackSizing::MinMax(_)
992 ));
993 }
994
995 #[test]
996 fn test_parse_grid_template_minmax_complex() {
997 let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
998 assert_eq!(result.tracks.len(), 1);
999 }
1000
1001 #[test]
1002 fn test_parse_grid_template_fit_content() {
1003 let result = parse_grid_template("fit-content(200px)").unwrap();
1004 assert_eq!(result.tracks.len(), 1);
1005 assert!(matches!(
1006 result.tracks.as_ref()[0],
1007 GridTrackSizing::FitContent(_)
1008 ));
1009 }
1010
1011 #[test]
1012 fn test_parse_grid_template_mixed() {
1013 let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1014 assert_eq!(result.tracks.len(), 4);
1015 }
1016
1017 #[test]
1018 fn test_parse_grid_template_percent() {
1019 let result = parse_grid_template("25% 50% 25%").unwrap();
1020 assert_eq!(result.tracks.len(), 3);
1021 }
1022
1023 #[test]
1024 fn test_parse_grid_template_em_units() {
1025 let result = parse_grid_template("10em 20em 1fr").unwrap();
1026 assert_eq!(result.tracks.len(), 3);
1027 }
1028
1029 #[test]
1031 fn test_parse_grid_placement_auto() {
1032 let result = parse_grid_placement("auto").unwrap();
1033 assert!(matches!(result.grid_start, GridLine::Auto));
1034 assert!(matches!(result.grid_end, GridLine::Auto));
1035 }
1036
1037 #[test]
1038 fn test_parse_grid_placement_line_number() {
1039 let result = parse_grid_placement("1").unwrap();
1040 assert!(matches!(result.grid_start, GridLine::Line(1)));
1041 assert!(matches!(result.grid_end, GridLine::Auto));
1042 }
1043
1044 #[test]
1045 fn test_parse_grid_placement_negative_line() {
1046 let result = parse_grid_placement("-1").unwrap();
1047 assert!(matches!(result.grid_start, GridLine::Line(-1)));
1048 }
1049
1050 #[test]
1051 fn test_parse_grid_placement_span() {
1052 let result = parse_grid_placement("span 2").unwrap();
1053 assert!(matches!(result.grid_start, GridLine::Span(2)));
1054 }
1055
1056 #[test]
1057 fn test_parse_grid_placement_start_end() {
1058 let result = parse_grid_placement("1 / 3").unwrap();
1059 assert!(matches!(result.grid_start, GridLine::Line(1)));
1060 assert!(matches!(result.grid_end, GridLine::Line(3)));
1061 }
1062
1063 #[test]
1064 fn test_parse_grid_placement_span_end() {
1065 let result = parse_grid_placement("1 / span 2").unwrap();
1066 assert!(matches!(result.grid_start, GridLine::Line(1)));
1067 assert!(matches!(result.grid_end, GridLine::Span(2)));
1068 }
1069
1070 #[test]
1071 fn test_parse_grid_placement_named_line() {
1072 let result = parse_grid_placement("header-start").unwrap();
1073 assert!(matches!(result.grid_start, GridLine::Named(_)));
1074 }
1075
1076 #[test]
1077 fn test_parse_grid_placement_named_start_end() {
1078 let result = parse_grid_placement("header-start / header-end").unwrap();
1079 assert!(matches!(result.grid_start, GridLine::Named(_)));
1080 assert!(matches!(result.grid_end, GridLine::Named(_)));
1081 }
1082
1083 #[test]
1085 fn test_parse_grid_template_whitespace() {
1086 let result = parse_grid_template(" 100px 200px ").unwrap();
1087 assert_eq!(result.tracks.len(), 2);
1088 }
1089
1090 #[test]
1091 fn test_parse_grid_placement_whitespace() {
1092 let result = parse_grid_placement(" 1 / 3 ").unwrap();
1093 assert!(matches!(result.grid_start, GridLine::Line(1)));
1094 assert!(matches!(result.grid_end, GridLine::Line(3)));
1095 }
1096
1097 #[test]
1098 fn test_parse_grid_template_zero_fr() {
1099 let result = parse_grid_template("0fr").unwrap();
1100 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1101 }
1102
1103 #[test]
1104 fn test_parse_grid_placement_zero_line() {
1105 let result = parse_grid_placement("0").unwrap();
1106 assert!(matches!(result.grid_start, GridLine::Line(0)));
1107 }
1108
1109 #[test]
1111 fn test_parse_grid_template_repeat_fr() {
1112 let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1113 assert_eq!(result.tracks.len(), 3);
1114 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
1115 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1116 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1117 }
1118
1119 #[test]
1120 fn test_parse_grid_template_repeat_px() {
1121 let result = parse_grid_template("repeat(2, 100px)").unwrap();
1122 assert_eq!(result.tracks.len(), 2);
1123 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1124 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
1125 }
1126
1127 #[test]
1128 fn test_parse_grid_template_repeat_multiple_tracks() {
1129 let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1131 assert_eq!(result.tracks.len(), 4);
1132 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1133 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1134 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
1135 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
1136 }
1137
1138 #[test]
1139 fn test_parse_grid_template_repeat_with_other_tracks() {
1140 let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1142 assert_eq!(result.tracks.len(), 4);
1143 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1144 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1145 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1146 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1147 }
1148
1149 #[test]
1150 fn test_parse_grid_template_repeat_minmax() {
1151 let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1152 assert_eq!(result.tracks.len(), 3);
1153 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
1154 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
1155 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
1156 }
1157}
1158
1159#[repr(C)]
1164#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1165pub struct GridAreaDefinition {
1166 pub name: AzString,
1167 pub row_start: u16,
1168 pub row_end: u16,
1169 pub column_start: u16,
1170 pub column_end: u16,
1171}
1172
1173impl_option!(
1174 GridAreaDefinition,
1175 OptionGridAreaDefinition,
1176 copy = false,
1177 [Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1178);
1179
1180impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
1181impl_vec_clone!(
1182 GridAreaDefinition,
1183 GridAreaDefinitionVec,
1184 GridAreaDefinitionVecDestructor
1185);
1186impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1187impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1188impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1189impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1190impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1191impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1192impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1193
1194#[repr(C)]
1204#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1205pub struct GridTemplateAreas {
1206 pub areas: GridAreaDefinitionVec,
1207}
1208
1209impl Default for GridTemplateAreas {
1210 fn default() -> Self {
1211 GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
1212 }
1213}
1214
1215impl PrintAsCssValue for GridTemplateAreas {
1216 fn print_as_css_value(&self) -> String {
1217 let areas_slice = self.areas.as_ref();
1218 if areas_slice.is_empty() {
1219 return "none".to_string();
1220 }
1221 let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1223 let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1224 let num_rows = (max_row - 1) as usize;
1225 let num_cols = (max_col - 1) as usize;
1226 let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1227 for area in areas_slice {
1228 for r in (area.row_start as usize - 1)..(area.row_end as usize - 1) {
1229 for c in (area.column_start as usize - 1)..(area.column_end as usize - 1) {
1230 if r < num_rows && c < num_cols {
1231 grid[r][c] = area.name.as_str().to_string();
1232 }
1233 }
1234 }
1235 }
1236 grid.iter()
1237 .map(|row| format!("\"{}\"", row.join(" ")))
1238 .collect::<Vec<_>>()
1239 .join(" ")
1240 }
1241}
1242
1243#[cfg(feature = "parser")]
1251pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1252 let input = input.trim();
1253 if input == "none" {
1254 return Ok(GridTemplateAreas::default());
1255 }
1256
1257 let mut rows: Vec<Vec<String>> = Vec::new();
1259 let mut i = 0;
1260 let bytes = input.as_bytes();
1261 while i < bytes.len() {
1262 if bytes[i] == b'"' || bytes[i] == b'\'' {
1263 let quote = bytes[i];
1264 i += 1;
1265 let start = i;
1266 while i < bytes.len() && bytes[i] != quote {
1267 i += 1;
1268 }
1269 if i >= bytes.len() {
1270 return Err(());
1271 }
1272 let row_str = &input[start..i];
1273 let cells: Vec<String> = row_str.split_whitespace().map(|s| s.to_string()).collect();
1274 if cells.is_empty() {
1275 return Err(());
1276 }
1277 rows.push(cells);
1278 i += 1; } else {
1280 i += 1;
1281 }
1282 }
1283
1284 if rows.is_empty() {
1285 return Err(());
1286 }
1287
1288 let num_cols = rows[0].len();
1290 for row in &rows {
1291 if row.len() != num_cols {
1292 return Err(());
1293 }
1294 }
1295
1296 use alloc::collections::BTreeMap;
1298 let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1299
1300 for (row_idx, row) in rows.iter().enumerate() {
1301 for (col_idx, cell) in row.iter().enumerate() {
1302 if cell == "." {
1303 continue; }
1305 let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
1306 entry.0 = entry.0.min(row_idx);
1307 entry.1 = entry.1.max(row_idx);
1308 entry.2 = entry.2.min(col_idx);
1309 entry.3 = entry.3.max(col_idx);
1310 }
1311 }
1312
1313 let mut areas = Vec::new();
1315 for (name, (min_row, max_row, min_col, max_col)) in area_map {
1316 areas.push(GridAreaDefinition {
1317 name: name.into(),
1318 row_start: (min_row + 1) as u16,
1319 row_end: (max_row + 2) as u16, column_start: (min_col + 1) as u16,
1321 column_end: (max_col + 2) as u16,
1322 });
1323 }
1324
1325 Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
1326}