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)]
41pub enum GridTrackSizing {
42 Fixed(PixelValue),
44 Fr(i32),
46 MinContent,
48 MaxContent,
50 Auto,
52 MinMax(GridMinMax),
54 FitContent(PixelValue),
56}
57
58impl_option!(
59 GridTrackSizing,
60 OptionGridTrackSizing,
61 copy = false,
62 [Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
63);
64
65impl core::fmt::Debug for GridTrackSizing {
66 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
67 write!(f, "{}", self.print_as_css_value())
68 }
69}
70
71impl Default for GridTrackSizing {
72 fn default() -> Self {
73 GridTrackSizing::Auto
74 }
75}
76
77impl PrintAsCssValue for GridTrackSizing {
78 fn print_as_css_value(&self) -> String {
79 match self {
80 GridTrackSizing::Fixed(px) => px.print_as_css_value(),
81 GridTrackSizing::Fr(f) => format!("{}fr", f),
82 GridTrackSizing::MinContent => "min-content".to_string(),
83 GridTrackSizing::MaxContent => "max-content".to_string(),
84 GridTrackSizing::Auto => "auto".to_string(),
85 GridTrackSizing::MinMax(minmax) => {
86 format!(
87 "minmax({}, {})",
88 minmax.min.print_as_css_value(),
89 minmax.max.print_as_css_value()
90 )
91 }
92 GridTrackSizing::FitContent(size) => {
93 format!("fit-content({})", size.print_as_css_value())
94 }
95 }
96 }
97}
98
99impl_vec!(GridTrackSizing, GridTrackSizingVec, GridTrackSizingVecDestructor, GridTrackSizingVecDestructorType, GridTrackSizingVecSlice, OptionGridTrackSizing);
101impl_vec_clone!(
102 GridTrackSizing,
103 GridTrackSizingVec,
104 GridTrackSizingVecDestructor
105);
106impl_vec_debug!(GridTrackSizing, GridTrackSizingVec);
107impl_vec_partialeq!(GridTrackSizing, GridTrackSizingVec);
108impl_vec_eq!(GridTrackSizing, GridTrackSizingVec);
109impl_vec_partialord!(GridTrackSizing, GridTrackSizingVec);
110impl_vec_ord!(GridTrackSizing, GridTrackSizingVec);
111impl_vec_hash!(GridTrackSizing, GridTrackSizingVec);
112impl_vec_mut!(GridTrackSizing, GridTrackSizingVec);
113
114#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116#[repr(C)]
117pub struct GridTemplate {
118 pub tracks: GridTrackSizingVec,
119}
120
121impl core::fmt::Debug for GridTemplate {
122 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
123 write!(f, "{}", self.print_as_css_value())
124 }
125}
126
127impl Default for GridTemplate {
128 fn default() -> Self {
129 GridTemplate {
130 tracks: GridTrackSizingVec::from_vec(Vec::new()),
131 }
132 }
133}
134
135impl PrintAsCssValue for GridTemplate {
136 fn print_as_css_value(&self) -> String {
137 let tracks_slice = self.tracks.as_ref();
138 if tracks_slice.is_empty() {
139 "none".to_string()
140 } else {
141 tracks_slice
142 .iter()
143 .map(|t| t.print_as_css_value())
144 .collect::<Vec<_>>()
145 .join(" ")
146 }
147 }
148}
149
150#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
155#[repr(C)]
156pub struct GridAutoTracks {
157 pub tracks: GridTrackSizingVec,
158}
159
160impl core::fmt::Debug for GridAutoTracks {
161 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
162 write!(f, "{}", self.print_as_css_value())
163 }
164}
165
166impl Default for GridAutoTracks {
167 fn default() -> Self {
168 GridAutoTracks {
169 tracks: GridTrackSizingVec::from_vec(Vec::new()),
170 }
171 }
172}
173
174impl PrintAsCssValue for GridAutoTracks {
175 fn print_as_css_value(&self) -> String {
176 let tracks_slice = self.tracks.as_ref();
177 if tracks_slice.is_empty() {
178 "auto".to_string()
179 } else {
180 tracks_slice
181 .iter()
182 .map(|t| t.print_as_css_value())
183 .collect::<Vec<_>>()
184 .join(" ")
185 }
186 }
187}
188
189impl From<GridTemplate> for GridAutoTracks {
190 fn from(template: GridTemplate) -> Self {
191 GridAutoTracks {
192 tracks: template.tracks,
193 }
194 }
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
201#[repr(C)]
202pub struct NamedGridLine {
203 pub grid_line_name: AzString,
204 pub span_count: i32,
206}
207
208impl NamedGridLine {
209 pub fn create(name: AzString, span: Option<i32>) -> Self {
210 Self {
211 grid_line_name: name,
212 span_count: span.unwrap_or(0),
213 }
214 }
215
216 pub fn span(&self) -> Option<i32> {
217 if self.span_count == 0 {
218 None
219 } else {
220 Some(self.span_count)
221 }
222 }
223}
224
225#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
227#[repr(C, u8)]
228pub enum GridLine {
229 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
245impl Default for GridLine {
246 fn default() -> Self {
247 GridLine::Auto
248 }
249}
250
251impl PrintAsCssValue for GridLine {
252 fn print_as_css_value(&self) -> String {
253 match self {
254 GridLine::Auto => "auto".to_string(),
255 GridLine::Line(n) => n.to_string(),
256 GridLine::Named(named) => {
257 if named.span_count == 0 {
258 named.grid_line_name.as_str().to_string()
259 } else {
260 format!("{} {}", named.grid_line_name.as_str(), named.span_count)
261 }
262 }
263 GridLine::Span(n) => format!("span {}", n),
264 }
265 }
266}
267
268#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
270#[repr(C)]
271pub struct GridPlacement {
272 pub grid_start: GridLine,
273 pub grid_end: GridLine,
274}
275
276impl core::fmt::Debug for GridPlacement {
277 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
278 write!(f, "{}", self.print_as_css_value())
279 }
280}
281
282impl Default for GridPlacement {
283 fn default() -> Self {
284 GridPlacement {
285 grid_start: GridLine::Auto,
286 grid_end: GridLine::Auto,
287 }
288 }
289}
290
291impl PrintAsCssValue for GridPlacement {
292 fn print_as_css_value(&self) -> String {
293 if self.grid_end == GridLine::Auto {
294 self.grid_start.print_as_css_value()
295 } else {
296 format!(
297 "{} / {}",
298 self.grid_start.print_as_css_value(),
299 self.grid_end.print_as_css_value()
300 )
301 }
302 }
303}
304
305#[cfg(feature = "parser")]
306#[derive(Clone, PartialEq)]
307pub enum GridParseError<'a> {
308 InvalidValue(&'a str),
309}
310
311#[cfg(feature = "parser")]
312impl_debug_as_display!(GridParseError<'a>);
313#[cfg(feature = "parser")]
314impl_display! { GridParseError<'a>, {
315 InvalidValue(e) => format!("Invalid grid value: \"{}\"", e),
316}}
317
318#[cfg(feature = "parser")]
319#[derive(Debug, Clone, PartialEq)]
320pub enum GridParseErrorOwned {
321 InvalidValue(String),
322}
323
324#[cfg(feature = "parser")]
325impl<'a> GridParseError<'a> {
326 pub fn to_contained(&self) -> GridParseErrorOwned {
327 match self {
328 GridParseError::InvalidValue(s) => GridParseErrorOwned::InvalidValue(s.to_string()),
329 }
330 }
331}
332
333#[cfg(feature = "parser")]
334impl GridParseErrorOwned {
335 pub fn to_shared<'a>(&'a self) -> GridParseError<'a> {
336 match self {
337 GridParseErrorOwned::InvalidValue(s) => GridParseError::InvalidValue(s.as_str()),
338 }
339 }
340}
341
342#[cfg(feature = "parser")]
343pub fn parse_grid_template<'a>(input: &'a str) -> Result<GridTemplate, GridParseError<'a>> {
344 use crate::props::basic::pixel::parse_pixel_value;
345
346 let input = input.trim();
347
348 if input == "none" {
349 return Ok(GridTemplate::default());
350 }
351
352 let mut tracks = Vec::new();
353 let mut current = String::new();
354 let mut paren_depth = 0;
355
356 for ch in input.chars() {
357 match ch {
358 '(' => {
359 paren_depth += 1;
360 current.push(ch);
361 }
362 ')' => {
363 paren_depth -= 1;
364 current.push(ch);
365 }
366 ' ' if paren_depth == 0 => {
367 if !current.trim().is_empty() {
368 let track_str = current.trim().to_string();
369 parse_grid_track_or_repeat(&track_str, &mut tracks)
370 .map_err(|_| GridParseError::InvalidValue(input))?;
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 parse_grid_track_or_repeat(&track_str, &mut tracks)
381 .map_err(|_| GridParseError::InvalidValue(input))?;
382 }
383
384 Ok(GridTemplate {
385 tracks: GridTrackSizingVec::from_vec(tracks),
386 })
387}
388
389#[cfg(feature = "parser")]
392fn parse_grid_track_or_repeat(input: &str, tracks: &mut Vec<GridTrackSizing>) -> Result<(), ()> {
393 let input = input.trim();
394
395 if input.starts_with("repeat(") && input.ends_with(')') {
397 let content = &input[7..input.len() - 1];
398 let comma_pos = content.find(',').ok_or(())?;
400 let count_str = content[..comma_pos].trim();
401 let track_list_str = content[comma_pos + 1..].trim();
402
403 let count: usize = count_str.parse().map_err(|_| ())?;
404 if count == 0 || count > 10000 {
405 return Err(());
406 }
407
408 let mut repeat_tracks = Vec::new();
410 let mut current = String::new();
411 let mut paren_depth = 0;
412 for ch in track_list_str.chars() {
413 match ch {
414 '(' => { paren_depth += 1; current.push(ch); }
415 ')' => { paren_depth -= 1; current.push(ch); }
416 ' ' if paren_depth == 0 => {
417 if !current.trim().is_empty() {
418 repeat_tracks.push(parse_grid_track_owned(current.trim())?);
419 current.clear();
420 }
421 }
422 _ => current.push(ch),
423 }
424 }
425 if !current.trim().is_empty() {
426 repeat_tracks.push(parse_grid_track_owned(current.trim())?);
427 }
428
429 for _ in 0..count {
431 tracks.extend(repeat_tracks.iter().cloned());
432 }
433 return Ok(());
434 }
435
436 tracks.push(parse_grid_track_owned(input)?);
438 Ok(())
439}
440
441#[cfg(feature = "parser")]
442fn parse_grid_track_owned(input: &str) -> Result<GridTrackSizing, ()> {
443 use crate::props::basic::pixel::parse_pixel_value;
444
445 let input = input.trim();
446
447 if input == "auto" {
448 return Ok(GridTrackSizing::Auto);
449 }
450
451 if input == "min-content" {
452 return Ok(GridTrackSizing::MinContent);
453 }
454
455 if input == "max-content" {
456 return Ok(GridTrackSizing::MaxContent);
457 }
458
459 if input.ends_with("fr") {
460 let num_str = &input[..input.len() - 2].trim();
461 if let Ok(num) = num_str.parse::<f32>() {
462 return Ok(GridTrackSizing::Fr((num * 100.0) as i32));
463 }
464 return Err(());
465 }
466
467 if input.starts_with("minmax(") && input.ends_with(')') {
468 let content = &input[7..input.len() - 1];
469 let parts: Vec<&str> = content.split(',').collect();
470 if parts.len() == 2 {
471 let min = parse_grid_track_owned(parts[0].trim())?;
472 let max = parse_grid_track_owned(parts[1].trim())?;
473 return Ok(GridTrackSizing::MinMax(GridMinMax {
474 min: Box::new(min),
475 max: Box::new(max),
476 }));
477 }
478 return Err(());
479 }
480
481 if input.starts_with("fit-content(") && input.ends_with(')') {
482 let size_str = &input[12..input.len() - 1].trim();
483 if let Ok(size) = parse_pixel_value(size_str) {
484 return Ok(GridTrackSizing::FitContent(size));
485 }
486 return Err(());
487 }
488
489 if let Ok(px) = parse_pixel_value(input) {
491 return Ok(GridTrackSizing::Fixed(px));
492 }
493
494 Err(())
495}
496
497#[cfg(feature = "parser")]
498pub fn parse_grid_placement<'a>(input: &'a str) -> Result<GridPlacement, GridParseError<'a>> {
499 let input = input.trim();
500
501 if input == "auto" {
502 return Ok(GridPlacement::default());
503 }
504
505 let parts: Vec<&str> = input.split('/').map(|s| s.trim()).collect();
507
508 let grid_start =
509 parse_grid_line_owned(parts[0]).map_err(|_| GridParseError::InvalidValue(input))?;
510 let grid_end = if parts.len() > 1 {
511 parse_grid_line_owned(parts[1]).map_err(|_| GridParseError::InvalidValue(input))?
512 } else {
513 GridLine::Auto
514 };
515
516 Ok(GridPlacement {
517 grid_start,
518 grid_end,
519 })
520}
521
522#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
526#[repr(C)]
527pub enum LayoutGridAutoFlow {
528 Row,
529 Column,
530 RowDense,
531 ColumnDense,
532}
533
534impl Default for LayoutGridAutoFlow {
535 fn default() -> Self {
536 LayoutGridAutoFlow::Row
537 }
538}
539
540impl crate::props::formatter::PrintAsCssValue for LayoutGridAutoFlow {
541 fn print_as_css_value(&self) -> alloc::string::String {
542 match self {
543 LayoutGridAutoFlow::Row => "row".to_string(),
544 LayoutGridAutoFlow::Column => "column".to_string(),
545 LayoutGridAutoFlow::RowDense => "row dense".to_string(),
546 LayoutGridAutoFlow::ColumnDense => "column dense".to_string(),
547 }
548 }
549}
550
551#[cfg(feature = "parser")]
552#[derive(Clone, PartialEq)]
553pub enum GridAutoFlowParseError<'a> {
554 InvalidValue(&'a str),
555}
556
557#[cfg(feature = "parser")]
558impl_debug_as_display!(GridAutoFlowParseError<'a>);
559#[cfg(feature = "parser")]
560impl_display! { GridAutoFlowParseError<'a>, {
561 InvalidValue(e) => format!("Invalid grid-auto-flow value: \"{}\"", e),
562}}
563
564#[cfg(feature = "parser")]
565#[derive(Debug, Clone, PartialEq)]
566pub enum GridAutoFlowParseErrorOwned {
567 InvalidValue(alloc::string::String),
568}
569
570#[cfg(feature = "parser")]
571impl<'a> GridAutoFlowParseError<'a> {
572 pub fn to_contained(&self) -> GridAutoFlowParseErrorOwned {
573 match self {
574 GridAutoFlowParseError::InvalidValue(s) => {
575 GridAutoFlowParseErrorOwned::InvalidValue(s.to_string())
576 }
577 }
578 }
579}
580
581#[cfg(feature = "parser")]
582impl GridAutoFlowParseErrorOwned {
583 pub fn to_shared<'a>(&'a self) -> GridAutoFlowParseError<'a> {
584 match self {
585 GridAutoFlowParseErrorOwned::InvalidValue(s) => {
586 GridAutoFlowParseError::InvalidValue(s.as_str())
587 }
588 }
589 }
590}
591
592#[cfg(feature = "parser")]
593pub fn parse_layout_grid_auto_flow<'a>(
594 input: &'a str,
595) -> Result<LayoutGridAutoFlow, GridAutoFlowParseError<'a>> {
596 match input.trim() {
597 "row" => Ok(LayoutGridAutoFlow::Row),
598 "column" => Ok(LayoutGridAutoFlow::Column),
599 "row dense" => Ok(LayoutGridAutoFlow::RowDense),
600 "column dense" => Ok(LayoutGridAutoFlow::ColumnDense),
601 "dense" => Ok(LayoutGridAutoFlow::RowDense),
602 _ => Err(GridAutoFlowParseError::InvalidValue(input)),
603 }
604}
605
606#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
610#[repr(C)]
611pub enum LayoutJustifySelf {
612 Auto,
613 Start,
614 End,
615 Center,
616 Stretch,
617}
618
619impl Default for LayoutJustifySelf {
620 fn default() -> Self {
621 Self::Auto
622 }
623}
624
625impl crate::props::formatter::PrintAsCssValue for LayoutJustifySelf {
626 fn print_as_css_value(&self) -> alloc::string::String {
627 match self {
628 LayoutJustifySelf::Auto => "auto".to_string(),
629 LayoutJustifySelf::Start => "start".to_string(),
630 LayoutJustifySelf::End => "end".to_string(),
631 LayoutJustifySelf::Center => "center".to_string(),
632 LayoutJustifySelf::Stretch => "stretch".to_string(),
633 }
634 }
635}
636
637#[cfg(feature = "parser")]
638#[derive(Clone, PartialEq)]
639pub enum JustifySelfParseError<'a> {
640 InvalidValue(&'a str),
641}
642
643#[cfg(feature = "parser")]
644#[derive(Debug, Clone, PartialEq)]
645pub enum JustifySelfParseErrorOwned {
646 InvalidValue(alloc::string::String),
647}
648
649#[cfg(feature = "parser")]
650impl<'a> JustifySelfParseError<'a> {
651 pub fn to_contained(&self) -> JustifySelfParseErrorOwned {
652 match self {
653 JustifySelfParseError::InvalidValue(s) => {
654 JustifySelfParseErrorOwned::InvalidValue(s.to_string())
655 }
656 }
657 }
658}
659
660#[cfg(feature = "parser")]
661impl JustifySelfParseErrorOwned {
662 pub fn to_shared<'a>(&'a self) -> JustifySelfParseError<'a> {
663 match self {
664 JustifySelfParseErrorOwned::InvalidValue(s) => {
665 JustifySelfParseError::InvalidValue(s.as_str())
666 }
667 }
668 }
669}
670
671#[cfg(feature = "parser")]
672impl_debug_as_display!(JustifySelfParseError<'a>);
673#[cfg(feature = "parser")]
674impl_display! { JustifySelfParseError<'a>, {
675 InvalidValue(e) => format!("Invalid justify-self value: \"{}\"", e),
676}}
677
678#[cfg(feature = "parser")]
679pub fn parse_layout_justify_self<'a>(
680 input: &'a str,
681) -> Result<LayoutJustifySelf, JustifySelfParseError<'a>> {
682 match input.trim() {
683 "auto" => Ok(LayoutJustifySelf::Auto),
684 "start" | "flex-start" => Ok(LayoutJustifySelf::Start),
685 "end" | "flex-end" => Ok(LayoutJustifySelf::End),
686 "center" => Ok(LayoutJustifySelf::Center),
687 "stretch" => Ok(LayoutJustifySelf::Stretch),
688 _ => Err(JustifySelfParseError::InvalidValue(input)),
689 }
690}
691
692#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
694#[repr(C)]
695pub enum LayoutJustifyItems {
696 Start,
697 End,
698 Center,
699 Stretch,
700}
701
702impl Default for LayoutJustifyItems {
703 fn default() -> Self {
704 Self::Stretch
705 }
706}
707
708impl crate::props::formatter::PrintAsCssValue for LayoutJustifyItems {
709 fn print_as_css_value(&self) -> alloc::string::String {
710 match self {
711 LayoutJustifyItems::Start => "start".to_string(),
712 LayoutJustifyItems::End => "end".to_string(),
713 LayoutJustifyItems::Center => "center".to_string(),
714 LayoutJustifyItems::Stretch => "stretch".to_string(),
715 }
716 }
717}
718
719#[cfg(feature = "parser")]
720#[derive(Clone, PartialEq)]
721pub enum JustifyItemsParseError<'a> {
722 InvalidValue(&'a str),
723}
724
725#[cfg(feature = "parser")]
726#[derive(Debug, Clone, PartialEq)]
727pub enum JustifyItemsParseErrorOwned {
728 InvalidValue(alloc::string::String),
729}
730
731#[cfg(feature = "parser")]
732impl<'a> JustifyItemsParseError<'a> {
733 pub fn to_contained(&self) -> JustifyItemsParseErrorOwned {
734 match self {
735 JustifyItemsParseError::InvalidValue(s) => {
736 JustifyItemsParseErrorOwned::InvalidValue(s.to_string())
737 }
738 }
739 }
740}
741
742#[cfg(feature = "parser")]
743impl JustifyItemsParseErrorOwned {
744 pub fn to_shared<'a>(&'a self) -> JustifyItemsParseError<'a> {
745 match self {
746 JustifyItemsParseErrorOwned::InvalidValue(s) => {
747 JustifyItemsParseError::InvalidValue(s.as_str())
748 }
749 }
750 }
751}
752
753#[cfg(feature = "parser")]
754impl_debug_as_display!(JustifyItemsParseError<'a>);
755#[cfg(feature = "parser")]
756impl_display! { JustifyItemsParseError<'a>, {
757 InvalidValue(e) => format!("Invalid justify-items value: \"{}\"", e),
758}}
759
760#[cfg(feature = "parser")]
761pub fn parse_layout_justify_items<'a>(
762 input: &'a str,
763) -> Result<LayoutJustifyItems, JustifyItemsParseError<'a>> {
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: crate::props::basic::pixel::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 crate::props::formatter::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 LayoutGridAutoFlow::Row => "Row",
801 LayoutGridAutoFlow::Column => "Column",
802 LayoutGridAutoFlow::RowDense => "RowDense",
803 LayoutGridAutoFlow::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 LayoutJustifySelf::Auto => "Auto",
815 LayoutJustifySelf::Start => "Start",
816 LayoutJustifySelf::End => "End",
817 LayoutJustifySelf::Center => "Center",
818 LayoutJustifySelf::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 LayoutJustifyItems::Start => "Start",
830 LayoutJustifyItems::End => "End",
831 LayoutJustifyItems::Center => "Center",
832 LayoutJustifyItems::Stretch => "Stretch",
833 }
834 )
835 }
836}
837
838impl FormatAsRustCode for LayoutGap {
839 fn format_as_rust_code(&self, _tabs: usize) -> String {
840 format!("LayoutGap::Exact({})", self.inner)
843 }
844}
845
846impl FormatAsRustCode for GridTrackSizing {
847 fn format_as_rust_code(&self, tabs: usize) -> String {
848 use crate::format_rust_code::format_pixel_value;
849 match self {
850 GridTrackSizing::Fixed(pv) => {
851 format!("GridTrackSizing::Fixed({})", format_pixel_value(pv))
852 }
853 GridTrackSizing::Fr(f) => format!("GridTrackSizing::Fr({})", f),
854 GridTrackSizing::MinContent => "GridTrackSizing::MinContent".to_string(),
855 GridTrackSizing::MaxContent => "GridTrackSizing::MaxContent".to_string(),
856 GridTrackSizing::Auto => "GridTrackSizing::Auto".to_string(),
857 GridTrackSizing::MinMax(minmax) => {
858 format!(
859 "GridTrackSizing::MinMax(GridMinMax {{ min: Box::new({}), max: Box::new({}) }})",
860 minmax.min.format_as_rust_code(tabs),
861 minmax.max.format_as_rust_code(tabs)
862 )
863 }
864 GridTrackSizing::FitContent(pv) => {
865 format!("GridTrackSizing::FitContent({})", format_pixel_value(pv))
866 }
867 }
868 }
869}
870
871impl FormatAsRustCode for GridAutoTracks {
872 fn format_as_rust_code(&self, tabs: usize) -> String {
873 let tracks: Vec<String> = self
874 .tracks
875 .as_ref()
876 .iter()
877 .map(|t| t.format_as_rust_code(tabs))
878 .collect();
879 format!(
880 "GridAutoTracks {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
881 tracks.join(", ")
882 )
883 }
884}
885
886impl FormatAsRustCode for GridTemplateAreas {
887 fn format_as_rust_code(&self, _tabs: usize) -> String {
888 format!("GridTemplateAreas {{ areas: GridAreaDefinitionVec::from_vec(vec!{:?}) }}", self.areas.as_ref())
889 }
890}
891
892#[cfg(feature = "parser")]
893pub fn parse_layout_gap<'a>(
894 input: &'a str,
895) -> Result<LayoutGap, crate::props::basic::pixel::CssPixelValueParseError<'a>> {
896 crate::props::basic::pixel::parse_pixel_value(input).map(|p| LayoutGap { inner: p })
897}
898
899#[cfg(feature = "parser")]
900pub fn parse_grid_line_owned(input: &str) -> Result<GridLine, ()> {
901 let input = input.trim();
902
903 if input == "auto" {
904 return Ok(GridLine::Auto);
905 }
906
907 if input.starts_with("span ") {
908 let num_str = &input[5..].trim();
909 if let Ok(num) = num_str.parse::<i32>() {
910 return Ok(GridLine::Span(num));
911 }
912 return Err(());
913 }
914
915 if let Ok(num) = input.parse::<i32>() {
917 return Ok(GridLine::Line(num));
918 }
919
920 Ok(GridLine::Named(NamedGridLine::create(
922 input.to_string().into(),
923 None,
924 )))
925}
926
927#[cfg(all(test, feature = "parser"))]
928mod tests {
929 use super::*;
930
931 #[test]
933 fn test_parse_grid_template_none() {
934 let result = parse_grid_template("none").unwrap();
935 assert_eq!(result.tracks.len(), 0);
936 }
937
938 #[test]
939 fn test_parse_grid_template_single_px() {
940 let result = parse_grid_template("100px").unwrap();
941 assert_eq!(result.tracks.len(), 1);
942 assert!(matches!(
943 result.tracks.as_ref()[0],
944 GridTrackSizing::Fixed(_)
945 ));
946 }
947
948 #[test]
949 fn test_parse_grid_template_multiple_tracks() {
950 let result = parse_grid_template("100px 200px 1fr").unwrap();
951 assert_eq!(result.tracks.len(), 3);
952 }
953
954 #[test]
955 fn test_parse_grid_template_fr_units() {
956 let result = parse_grid_template("1fr 2fr 1fr").unwrap();
957 assert_eq!(result.tracks.len(), 3);
958 assert!(matches!(
959 result.tracks.as_ref()[0],
960 GridTrackSizing::Fr(100)
961 ));
962 assert!(matches!(
963 result.tracks.as_ref()[1],
964 GridTrackSizing::Fr(200)
965 ));
966 }
967
968 #[test]
969 fn test_parse_grid_template_fractional_fr() {
970 let result = parse_grid_template("0.5fr 1.5fr").unwrap();
971 assert_eq!(result.tracks.len(), 2);
972 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(50)));
973 assert!(matches!(
974 result.tracks.as_ref()[1],
975 GridTrackSizing::Fr(150)
976 ));
977 }
978
979 #[test]
980 fn test_parse_grid_template_auto() {
981 let result = parse_grid_template("auto 100px auto").unwrap();
982 assert_eq!(result.tracks.len(), 3);
983 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Auto));
984 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Auto));
985 }
986
987 #[test]
988 fn test_parse_grid_template_min_max_content() {
989 let result = parse_grid_template("min-content max-content auto").unwrap();
990 assert_eq!(result.tracks.len(), 3);
991 assert!(matches!(
992 result.tracks.as_ref()[0],
993 GridTrackSizing::MinContent
994 ));
995 assert!(matches!(
996 result.tracks.as_ref()[1],
997 GridTrackSizing::MaxContent
998 ));
999 }
1000
1001 #[test]
1002 fn test_parse_grid_template_minmax() {
1003 let result = parse_grid_template("minmax(100px, 1fr)").unwrap();
1004 assert_eq!(result.tracks.len(), 1);
1005 assert!(matches!(
1006 result.tracks.as_ref()[0],
1007 GridTrackSizing::MinMax(_)
1008 ));
1009 }
1010
1011 #[test]
1012 fn test_parse_grid_template_minmax_complex() {
1013 let result = parse_grid_template("minmax(min-content, max-content)").unwrap();
1014 assert_eq!(result.tracks.len(), 1);
1015 }
1016
1017 #[test]
1018 fn test_parse_grid_template_fit_content() {
1019 let result = parse_grid_template("fit-content(200px)").unwrap();
1020 assert_eq!(result.tracks.len(), 1);
1021 assert!(matches!(
1022 result.tracks.as_ref()[0],
1023 GridTrackSizing::FitContent(_)
1024 ));
1025 }
1026
1027 #[test]
1028 fn test_parse_grid_template_mixed() {
1029 let result = parse_grid_template("100px minmax(100px, 1fr) auto 2fr").unwrap();
1030 assert_eq!(result.tracks.len(), 4);
1031 }
1032
1033 #[test]
1034 fn test_parse_grid_template_percent() {
1035 let result = parse_grid_template("25% 50% 25%").unwrap();
1036 assert_eq!(result.tracks.len(), 3);
1037 }
1038
1039 #[test]
1040 fn test_parse_grid_template_em_units() {
1041 let result = parse_grid_template("10em 20em 1fr").unwrap();
1042 assert_eq!(result.tracks.len(), 3);
1043 }
1044
1045 #[test]
1047 fn test_parse_grid_placement_auto() {
1048 let result = parse_grid_placement("auto").unwrap();
1049 assert!(matches!(result.grid_start, GridLine::Auto));
1050 assert!(matches!(result.grid_end, GridLine::Auto));
1051 }
1052
1053 #[test]
1054 fn test_parse_grid_placement_line_number() {
1055 let result = parse_grid_placement("1").unwrap();
1056 assert!(matches!(result.grid_start, GridLine::Line(1)));
1057 assert!(matches!(result.grid_end, GridLine::Auto));
1058 }
1059
1060 #[test]
1061 fn test_parse_grid_placement_negative_line() {
1062 let result = parse_grid_placement("-1").unwrap();
1063 assert!(matches!(result.grid_start, GridLine::Line(-1)));
1064 }
1065
1066 #[test]
1067 fn test_parse_grid_placement_span() {
1068 let result = parse_grid_placement("span 2").unwrap();
1069 assert!(matches!(result.grid_start, GridLine::Span(2)));
1070 }
1071
1072 #[test]
1073 fn test_parse_grid_placement_start_end() {
1074 let result = parse_grid_placement("1 / 3").unwrap();
1075 assert!(matches!(result.grid_start, GridLine::Line(1)));
1076 assert!(matches!(result.grid_end, GridLine::Line(3)));
1077 }
1078
1079 #[test]
1080 fn test_parse_grid_placement_span_end() {
1081 let result = parse_grid_placement("1 / span 2").unwrap();
1082 assert!(matches!(result.grid_start, GridLine::Line(1)));
1083 assert!(matches!(result.grid_end, GridLine::Span(2)));
1084 }
1085
1086 #[test]
1087 fn test_parse_grid_placement_named_line() {
1088 let result = parse_grid_placement("header-start").unwrap();
1089 assert!(matches!(result.grid_start, GridLine::Named(_)));
1090 }
1091
1092 #[test]
1093 fn test_parse_grid_placement_named_start_end() {
1094 let result = parse_grid_placement("header-start / header-end").unwrap();
1095 assert!(matches!(result.grid_start, GridLine::Named(_)));
1096 assert!(matches!(result.grid_end, GridLine::Named(_)));
1097 }
1098
1099 #[test]
1101 fn test_parse_grid_template_whitespace() {
1102 let result = parse_grid_template(" 100px 200px ").unwrap();
1103 assert_eq!(result.tracks.len(), 2);
1104 }
1105
1106 #[test]
1107 fn test_parse_grid_placement_whitespace() {
1108 let result = parse_grid_placement(" 1 / 3 ").unwrap();
1109 assert!(matches!(result.grid_start, GridLine::Line(1)));
1110 assert!(matches!(result.grid_end, GridLine::Line(3)));
1111 }
1112
1113 #[test]
1114 fn test_parse_grid_template_zero_fr() {
1115 let result = parse_grid_template("0fr").unwrap();
1116 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(0)));
1117 }
1118
1119 #[test]
1120 fn test_parse_grid_placement_zero_line() {
1121 let result = parse_grid_placement("0").unwrap();
1122 assert!(matches!(result.grid_start, GridLine::Line(0)));
1123 }
1124
1125 #[test]
1127 fn test_parse_grid_template_repeat_fr() {
1128 let result = parse_grid_template("repeat(3, 1fr)").unwrap();
1129 assert_eq!(result.tracks.len(), 3);
1130 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fr(100)));
1131 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1132 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1133 }
1134
1135 #[test]
1136 fn test_parse_grid_template_repeat_px() {
1137 let result = parse_grid_template("repeat(2, 100px)").unwrap();
1138 assert_eq!(result.tracks.len(), 2);
1139 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1140 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fixed(_)));
1141 }
1142
1143 #[test]
1144 fn test_parse_grid_template_repeat_multiple_tracks() {
1145 let result = parse_grid_template("repeat(2, 100px 1fr)").unwrap();
1147 assert_eq!(result.tracks.len(), 4);
1148 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1149 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1150 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fixed(_)));
1151 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Fr(100)));
1152 }
1153
1154 #[test]
1155 fn test_parse_grid_template_repeat_with_other_tracks() {
1156 let result = parse_grid_template("100px repeat(2, 1fr) auto").unwrap();
1158 assert_eq!(result.tracks.len(), 4);
1159 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::Fixed(_)));
1160 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::Fr(100)));
1161 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::Fr(100)));
1162 assert!(matches!(result.tracks.as_ref()[3], GridTrackSizing::Auto));
1163 }
1164
1165 #[test]
1166 fn test_parse_grid_template_repeat_minmax() {
1167 let result = parse_grid_template("repeat(3, minmax(100px, 1fr))").unwrap();
1168 assert_eq!(result.tracks.len(), 3);
1169 assert!(matches!(result.tracks.as_ref()[0], GridTrackSizing::MinMax(_)));
1170 assert!(matches!(result.tracks.as_ref()[1], GridTrackSizing::MinMax(_)));
1171 assert!(matches!(result.tracks.as_ref()[2], GridTrackSizing::MinMax(_)));
1172 }
1173}
1174
1175#[repr(C)]
1180#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1181pub struct GridAreaDefinition {
1182 pub name: AzString,
1183 pub row_start: u16,
1184 pub row_end: u16,
1185 pub column_start: u16,
1186 pub column_end: u16,
1187}
1188
1189impl_option!(
1190 GridAreaDefinition,
1191 OptionGridAreaDefinition,
1192 copy = false,
1193 [Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1194);
1195
1196impl_vec!(GridAreaDefinition, GridAreaDefinitionVec, GridAreaDefinitionVecDestructor, GridAreaDefinitionVecDestructorType, GridAreaDefinitionVecSlice, OptionGridAreaDefinition);
1197impl_vec_clone!(
1198 GridAreaDefinition,
1199 GridAreaDefinitionVec,
1200 GridAreaDefinitionVecDestructor
1201);
1202impl_vec_debug!(GridAreaDefinition, GridAreaDefinitionVec);
1203impl_vec_partialeq!(GridAreaDefinition, GridAreaDefinitionVec);
1204impl_vec_eq!(GridAreaDefinition, GridAreaDefinitionVec);
1205impl_vec_partialord!(GridAreaDefinition, GridAreaDefinitionVec);
1206impl_vec_ord!(GridAreaDefinition, GridAreaDefinitionVec);
1207impl_vec_hash!(GridAreaDefinition, GridAreaDefinitionVec);
1208impl_vec_mut!(GridAreaDefinition, GridAreaDefinitionVec);
1209
1210#[repr(C)]
1220#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1221pub struct GridTemplateAreas {
1222 pub areas: GridAreaDefinitionVec,
1223}
1224
1225impl Default for GridTemplateAreas {
1226 fn default() -> Self {
1227 GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(Vec::new()) }
1228 }
1229}
1230
1231impl PrintAsCssValue for GridTemplateAreas {
1232 fn print_as_css_value(&self) -> String {
1233 let areas_slice = self.areas.as_ref();
1234 if areas_slice.is_empty() {
1235 return "none".to_string();
1236 }
1237 let max_row = areas_slice.iter().map(|a| a.row_end).max().unwrap_or(1);
1239 let max_col = areas_slice.iter().map(|a| a.column_end).max().unwrap_or(1);
1240 let num_rows = (max_row - 1) as usize;
1241 let num_cols = (max_col - 1) as usize;
1242 let mut grid: Vec<Vec<String>> = vec![vec![".".to_string(); num_cols]; num_rows];
1243 for area in areas_slice {
1244 for r in (area.row_start as usize - 1)..(area.row_end as usize - 1) {
1245 for c in (area.column_start as usize - 1)..(area.column_end as usize - 1) {
1246 if r < num_rows && c < num_cols {
1247 grid[r][c] = area.name.as_str().to_string();
1248 }
1249 }
1250 }
1251 }
1252 grid.iter()
1253 .map(|row| format!("\"{}\"", row.join(" ")))
1254 .collect::<Vec<_>>()
1255 .join(" ")
1256 }
1257}
1258
1259pub fn parse_grid_template_areas(input: &str) -> Result<GridTemplateAreas, ()> {
1267 let input = input.trim();
1268 if input == "none" {
1269 return Ok(GridTemplateAreas::default());
1270 }
1271
1272 let mut rows: Vec<Vec<String>> = Vec::new();
1274 let mut i = 0;
1275 let bytes = input.as_bytes();
1276 while i < bytes.len() {
1277 if bytes[i] == b'"' || bytes[i] == b'\'' {
1278 let quote = bytes[i];
1279 i += 1;
1280 let start = i;
1281 while i < bytes.len() && bytes[i] != quote {
1282 i += 1;
1283 }
1284 if i >= bytes.len() {
1285 return Err(());
1286 }
1287 let row_str = &input[start..i];
1288 let cells: Vec<String> = row_str.split_whitespace().map(|s| s.to_string()).collect();
1289 if cells.is_empty() {
1290 return Err(());
1291 }
1292 rows.push(cells);
1293 i += 1; } else {
1295 i += 1;
1296 }
1297 }
1298
1299 if rows.is_empty() {
1300 return Err(());
1301 }
1302
1303 let num_cols = rows[0].len();
1305 for row in &rows {
1306 if row.len() != num_cols {
1307 return Err(());
1308 }
1309 }
1310
1311 use alloc::collections::BTreeMap;
1313 let mut area_map: BTreeMap<String, (usize, usize, usize, usize)> = BTreeMap::new();
1314
1315 for (row_idx, row) in rows.iter().enumerate() {
1316 for (col_idx, cell) in row.iter().enumerate() {
1317 if cell == "." {
1318 continue; }
1320 let entry = area_map.entry(cell.clone()).or_insert((row_idx, row_idx, col_idx, col_idx));
1321 entry.0 = entry.0.min(row_idx);
1322 entry.1 = entry.1.max(row_idx);
1323 entry.2 = entry.2.min(col_idx);
1324 entry.3 = entry.3.max(col_idx);
1325 }
1326 }
1327
1328 let mut areas = Vec::new();
1330 for (name, (min_row, max_row, min_col, max_col)) in area_map {
1331 areas.push(GridAreaDefinition {
1332 name: name.into(),
1333 row_start: (min_row + 1) as u16,
1334 row_end: (max_row + 2) as u16, column_start: (min_col + 1) as u16,
1336 column_end: (max_col + 2) as u16,
1337 });
1338 }
1339
1340 Ok(GridTemplateAreas { areas: GridAreaDefinitionVec::from_vec(areas) })
1341}