1use alloc::string::{String, ToString};
4use crate::corety::{AzString, OptionF32};
5
6use crate::props::formatter::PrintAsCssValue;
7
8#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(C)]
16pub enum LayoutOverflow {
17 Scroll,
19 Auto,
21 Hidden,
23 #[default]
26 Visible,
27 Clip,
29}
30
31impl LayoutOverflow {
32 pub fn needs_scrollbar(&self, currently_overflowing: bool) -> bool {
41 match self {
42 LayoutOverflow::Scroll => true,
43 LayoutOverflow::Auto => currently_overflowing,
44 LayoutOverflow::Hidden | LayoutOverflow::Visible | LayoutOverflow::Clip => false,
45 }
46 }
47
48 pub fn is_clipped(&self) -> bool {
54 matches!(
56 self,
57 LayoutOverflow::Hidden
58 | LayoutOverflow::Clip
59 | LayoutOverflow::Auto
60 | LayoutOverflow::Scroll
61 )
62 }
63
64 pub fn is_scroll(&self) -> bool {
67 matches!(self, LayoutOverflow::Scroll)
68 }
69
70 pub fn is_overflow_visible(&self) -> bool {
73 *self == LayoutOverflow::Visible
74 }
75
76 pub fn is_overflow_hidden(&self) -> bool {
78 *self == LayoutOverflow::Hidden
79 }
80
81 pub fn resolve_computed(self, other_axis: LayoutOverflow) -> LayoutOverflow {
86 let other_is_scrollable = !matches!(other_axis, LayoutOverflow::Visible | LayoutOverflow::Clip);
87 if other_is_scrollable {
88 match self {
89 LayoutOverflow::Visible => LayoutOverflow::Auto,
90 LayoutOverflow::Clip => LayoutOverflow::Hidden,
91 other => other,
92 }
93 } else {
94 self
95 }
96 }
97}
98
99impl PrintAsCssValue for LayoutOverflow {
100 fn print_as_css_value(&self) -> String {
101 String::from(match self {
102 LayoutOverflow::Scroll => "scroll",
103 LayoutOverflow::Auto => "auto",
104 LayoutOverflow::Hidden => "hidden",
105 LayoutOverflow::Visible => "visible",
106 LayoutOverflow::Clip => "clip",
107 })
108 }
109}
110
111#[derive(Clone, PartialEq, Eq)]
115pub enum LayoutOverflowParseError<'a> {
116 InvalidValue(&'a str),
118}
119
120impl_debug_as_display!(LayoutOverflowParseError<'a>);
121impl_display! { LayoutOverflowParseError<'a>, {
122 InvalidValue(val) => format!(
123 "Invalid overflow value: \"{}\". Expected 'scroll', 'auto', 'hidden', 'visible', or 'clip'.", val
124 ),
125}}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129#[repr(C, u8)]
130pub enum LayoutOverflowParseErrorOwned {
131 InvalidValue(AzString),
132}
133
134impl<'a> LayoutOverflowParseError<'a> {
135 pub fn to_contained(&self) -> LayoutOverflowParseErrorOwned {
137 match self {
138 LayoutOverflowParseError::InvalidValue(s) => {
139 LayoutOverflowParseErrorOwned::InvalidValue(s.to_string().into())
140 }
141 }
142 }
143}
144
145impl LayoutOverflowParseErrorOwned {
146 pub fn to_shared<'a>(&'a self) -> LayoutOverflowParseError<'a> {
148 match self {
149 LayoutOverflowParseErrorOwned::InvalidValue(s) => {
150 LayoutOverflowParseError::InvalidValue(s.as_str())
151 }
152 }
153 }
154}
155
156#[cfg(feature = "parser")]
157pub fn parse_layout_overflow<'a>(
159 input: &'a str,
160) -> Result<LayoutOverflow, LayoutOverflowParseError<'a>> {
161 let input_trimmed = input.trim();
162 match input_trimmed {
163 "scroll" => Ok(LayoutOverflow::Scroll),
164 "auto" | "overlay" => Ok(LayoutOverflow::Auto), "hidden" => Ok(LayoutOverflow::Hidden),
166 "visible" => Ok(LayoutOverflow::Visible),
167 "clip" => Ok(LayoutOverflow::Clip),
168 _ => Err(LayoutOverflowParseError::InvalidValue(input)),
169 }
170}
171
172#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
181#[repr(C)]
182pub enum StyleScrollbarGutter {
183 #[default]
185 Auto,
186 Stable,
188 StableBothEdges,
190}
191
192impl PrintAsCssValue for StyleScrollbarGutter {
193 fn print_as_css_value(&self) -> String {
194 String::from(match self {
195 StyleScrollbarGutter::Auto => "auto",
196 StyleScrollbarGutter::Stable => "stable",
197 StyleScrollbarGutter::StableBothEdges => "stable both-edges",
198 })
199 }
200}
201
202#[derive(Clone, PartialEq, Eq)]
206pub enum StyleScrollbarGutterParseError<'a> {
207 InvalidValue(&'a str),
209}
210
211impl_debug_as_display!(StyleScrollbarGutterParseError<'a>);
212impl_display! { StyleScrollbarGutterParseError<'a>, {
213 InvalidValue(val) => format!(
214 "Invalid scrollbar-gutter value: \"{}\". Expected 'auto', 'stable', or 'stable both-edges'.", val
215 ),
216}}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220#[repr(C, u8)]
221pub enum StyleScrollbarGutterParseErrorOwned {
222 InvalidValue(AzString),
223}
224
225impl<'a> StyleScrollbarGutterParseError<'a> {
226 pub fn to_contained(&self) -> StyleScrollbarGutterParseErrorOwned {
228 match self {
229 StyleScrollbarGutterParseError::InvalidValue(s) => {
230 StyleScrollbarGutterParseErrorOwned::InvalidValue(s.to_string().into())
231 }
232 }
233 }
234}
235
236impl StyleScrollbarGutterParseErrorOwned {
237 pub fn to_shared<'a>(&'a self) -> StyleScrollbarGutterParseError<'a> {
239 match self {
240 StyleScrollbarGutterParseErrorOwned::InvalidValue(s) => {
241 StyleScrollbarGutterParseError::InvalidValue(s.as_str())
242 }
243 }
244 }
245}
246
247#[cfg(feature = "parser")]
248pub fn parse_style_scrollbar_gutter<'a>(
250 input: &'a str,
251) -> Result<StyleScrollbarGutter, StyleScrollbarGutterParseError<'a>> {
252 let input_trimmed = input.trim();
253 match input_trimmed {
254 "auto" => Ok(StyleScrollbarGutter::Auto),
255 "stable" => Ok(StyleScrollbarGutter::Stable),
256 "stable both-edges" => Ok(StyleScrollbarGutter::StableBothEdges),
257 _ => Err(StyleScrollbarGutterParseError::InvalidValue(input)),
258 }
259}
260
261#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
269#[repr(C)]
270pub enum VisualBox {
271 ContentBox,
273 #[default]
275 PaddingBox,
276 BorderBox,
278}
279
280impl PrintAsCssValue for VisualBox {
281 fn print_as_css_value(&self) -> String {
282 String::from(match self {
283 VisualBox::ContentBox => "content-box",
284 VisualBox::PaddingBox => "padding-box",
285 VisualBox::BorderBox => "border-box",
286 })
287 }
288}
289
290#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
299#[repr(C)]
300pub struct StyleOverflowClipMargin {
301 pub clip_edge: VisualBox,
303 pub inner: crate::props::basic::pixel::PixelValue,
305}
306
307impl PrintAsCssValue for StyleOverflowClipMargin {
308 fn print_as_css_value(&self) -> String {
309 let edge = self.clip_edge.print_as_css_value();
310 let len = self.inner.print_as_css_value();
311 #[allow(clippy::float_cmp)] if self.inner.number.get() == 0.0 {
313 edge
314 } else if self.clip_edge == VisualBox::PaddingBox {
315 len
316 } else {
317 format!("{} {}", edge, len)
318 }
319 }
320}
321
322#[derive(Clone, PartialEq, Eq)]
324pub enum StyleOverflowClipMarginParseError<'a> {
325 InvalidValue(&'a str),
327}
328
329impl_debug_as_display!(StyleOverflowClipMarginParseError<'a>);
330impl_display! { StyleOverflowClipMarginParseError<'a>, {
331 InvalidValue(val) => format!("Invalid overflow-clip-margin value: \"{}\"", val),
332}}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
336#[repr(C, u8)]
337pub enum StyleOverflowClipMarginParseErrorOwned {
338 InvalidValue(AzString),
339}
340
341impl<'a> StyleOverflowClipMarginParseError<'a> {
342 pub fn to_contained(&self) -> StyleOverflowClipMarginParseErrorOwned {
344 match self {
345 StyleOverflowClipMarginParseError::InvalidValue(s) => {
346 StyleOverflowClipMarginParseErrorOwned::InvalidValue(s.to_string().into())
347 }
348 }
349 }
350}
351
352impl StyleOverflowClipMarginParseErrorOwned {
353 pub fn to_shared<'a>(&'a self) -> StyleOverflowClipMarginParseError<'a> {
355 match self {
356 StyleOverflowClipMarginParseErrorOwned::InvalidValue(s) => {
357 StyleOverflowClipMarginParseError::InvalidValue(s.as_str())
358 }
359 }
360 }
361}
362
363#[cfg(feature = "parser")]
364pub fn parse_style_overflow_clip_margin<'a>(
370 input: &'a str,
371) -> Result<StyleOverflowClipMargin, StyleOverflowClipMarginParseError<'a>> {
372 use crate::props::basic::pixel::parse_pixel_value;
373
374 let input_trimmed = input.trim();
375 let mut clip_edge = None;
376 let mut length = None;
377
378 for token in input_trimmed.split_whitespace() {
379 match token {
380 "content-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::ContentBox),
381 "padding-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::PaddingBox),
382 "border-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::BorderBox),
383 _ if length.is_none() => {
384 match parse_pixel_value(token) {
385 Ok(pv) => length = Some(pv),
386 Err(_) => return Err(StyleOverflowClipMarginParseError::InvalidValue(input)),
387 }
388 }
389 _ => return Err(StyleOverflowClipMarginParseError::InvalidValue(input)),
390 }
391 }
392
393 if clip_edge.is_none() && length.is_none() {
394 return Err(StyleOverflowClipMarginParseError::InvalidValue(input));
395 }
396
397 Ok(StyleOverflowClipMargin {
398 clip_edge: clip_edge.unwrap_or_default(),
399 inner: length.unwrap_or_default(),
400 })
401}
402
403#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
416#[repr(C)]
417pub struct StyleClipRect {
418 pub top: OptionF32,
420 pub right: OptionF32,
422 pub bottom: OptionF32,
424 pub left: OptionF32,
426}
427
428impl StyleClipRect {
429 pub fn resolve(
434 &self,
435 used_width: f32,
436 used_height: f32,
437 padding_left: f32,
438 padding_right: f32,
439 padding_top: f32,
440 padding_bottom: f32,
441 border_left: f32,
442 border_right: f32,
443 border_top: f32,
444 border_bottom: f32,
445 ) -> (f32, f32, f32, f32) {
446 let top = self.top.into_option().unwrap_or(0.0);
447 let left = self.left.into_option().unwrap_or(0.0);
448 let bottom = self
449 .bottom
450 .into_option()
451 .unwrap_or(used_height + padding_top + padding_bottom + border_top + border_bottom);
452 let right = self
453 .right
454 .into_option()
455 .unwrap_or(used_width + padding_left + padding_right + border_left + border_right);
456 (top, right, bottom, left)
457 }
458}
459
460impl PrintAsCssValue for StyleClipRect {
461 fn print_as_css_value(&self) -> String {
462 fn fmt_edge(o: &OptionF32) -> String {
463 match o.into_option() {
464 Some(v) => format!("{}px", v),
465 None => String::from("auto"),
466 }
467 }
468 format!(
469 "rect({}, {}, {}, {})",
470 fmt_edge(&self.top),
471 fmt_edge(&self.right),
472 fmt_edge(&self.bottom),
473 fmt_edge(&self.left)
474 )
475 }
476}
477
478#[derive(Clone, PartialEq, Eq)]
482pub enum StyleClipRectParseError<'a> {
483 InvalidValue(&'a str),
485}
486
487impl_debug_as_display!(StyleClipRectParseError<'a>);
488impl_display! { StyleClipRectParseError<'a>, {
489 InvalidValue(val) => format!(
490 "Invalid clip value: \"{}\". Expected 'auto' or 'rect(<top>, <right>, <bottom>, <left>)'.", val
491 ),
492}}
493
494#[derive(Debug, Clone, PartialEq, Eq)]
496#[repr(C, u8)]
497pub enum StyleClipRectParseErrorOwned {
498 InvalidValue(AzString),
499}
500
501impl<'a> StyleClipRectParseError<'a> {
502 pub fn to_contained(&self) -> StyleClipRectParseErrorOwned {
504 match self {
505 StyleClipRectParseError::InvalidValue(s) => {
506 StyleClipRectParseErrorOwned::InvalidValue(s.to_string().into())
507 }
508 }
509 }
510}
511
512impl StyleClipRectParseErrorOwned {
513 pub fn to_shared<'a>(&'a self) -> StyleClipRectParseError<'a> {
515 match self {
516 StyleClipRectParseErrorOwned::InvalidValue(s) => {
517 StyleClipRectParseError::InvalidValue(s.as_str())
518 }
519 }
520 }
521}
522
523#[cfg(feature = "parser")]
524fn parse_clip_edge<'a>(token: &'a str) -> Result<OptionF32, StyleClipRectParseError<'a>> {
525 use crate::props::basic::pixel::parse_pixel_value;
526
527 let token = token.trim();
528 if token.eq_ignore_ascii_case("auto") {
529 return Ok(OptionF32::None);
530 }
531 let pv = parse_pixel_value(token)
532 .map_err(|_| StyleClipRectParseError::InvalidValue(token))?;
533 Ok(OptionF32::Some(pv.number.get()))
534}
535
536#[cfg(feature = "parser")]
537pub fn parse_clip_rect<'a>(input: &'a str) -> Result<StyleClipRect, StyleClipRectParseError<'a>> {
546 let trimmed = input.trim();
547
548 if trimmed.eq_ignore_ascii_case("auto") {
549 return Ok(StyleClipRect::default());
550 }
551
552 let inner = trimmed
553 .strip_prefix("rect(")
554 .or_else(|| trimmed.strip_prefix("RECT("))
555 .and_then(|s| s.strip_suffix(')'))
556 .ok_or(StyleClipRectParseError::InvalidValue(input))?;
557
558 let inner = inner.trim();
559 let parts: alloc::vec::Vec<&str> = if inner.contains(',') {
560 inner.split(',').map(|s| s.trim()).collect()
561 } else {
562 inner.split_whitespace().collect()
563 };
564
565 if parts.len() != 4 {
566 return Err(StyleClipRectParseError::InvalidValue(input));
567 }
568
569 Ok(StyleClipRect {
570 top: parse_clip_edge(parts[0])?,
571 right: parse_clip_edge(parts[1])?,
572 bottom: parse_clip_edge(parts[2])?,
573 left: parse_clip_edge(parts[3])?,
574 })
575}
576
577#[cfg(all(test, feature = "parser"))]
578mod tests {
579 use super::*;
580
581 #[test]
582 fn test_parse_layout_overflow_valid() {
583 assert_eq!(
584 parse_layout_overflow("visible").unwrap(),
585 LayoutOverflow::Visible
586 );
587 assert_eq!(
588 parse_layout_overflow("hidden").unwrap(),
589 LayoutOverflow::Hidden
590 );
591 assert_eq!(parse_layout_overflow("clip").unwrap(), LayoutOverflow::Clip);
592 assert_eq!(
593 parse_layout_overflow("scroll").unwrap(),
594 LayoutOverflow::Scroll
595 );
596 assert_eq!(parse_layout_overflow("auto").unwrap(), LayoutOverflow::Auto);
597 }
598
599 #[test]
600 fn test_parse_layout_overflow_whitespace() {
601 assert_eq!(
602 parse_layout_overflow(" scroll ").unwrap(),
603 LayoutOverflow::Scroll
604 );
605 }
606
607 #[test]
608 fn test_parse_layout_overflow_invalid() {
609 assert!(parse_layout_overflow("none").is_err());
610 assert!(parse_layout_overflow("").is_err());
611 assert!(parse_layout_overflow("auto scroll").is_err());
612 assert!(parse_layout_overflow("hidden-x").is_err());
613 }
614
615 #[test]
616 fn test_needs_scrollbar() {
617 assert!(LayoutOverflow::Scroll.needs_scrollbar(false));
618 assert!(LayoutOverflow::Scroll.needs_scrollbar(true));
619 assert!(LayoutOverflow::Auto.needs_scrollbar(true));
620 assert!(!LayoutOverflow::Auto.needs_scrollbar(false));
621 assert!(!LayoutOverflow::Hidden.needs_scrollbar(true));
622 assert!(!LayoutOverflow::Visible.needs_scrollbar(true));
623 assert!(!LayoutOverflow::Clip.needs_scrollbar(true));
624 }
625
626 #[test]
627 fn test_parse_clip_rect_auto_keyword() {
628 let r = parse_clip_rect("auto").unwrap();
629 assert_eq!(r.top, OptionF32::None);
630 assert_eq!(r.right, OptionF32::None);
631 assert_eq!(r.bottom, OptionF32::None);
632 assert_eq!(r.left, OptionF32::None);
633 }
634
635 #[test]
636 fn test_parse_clip_rect_all_auto_in_rect() {
637 let r = parse_clip_rect("rect(auto, auto, auto, auto)").unwrap();
638 assert_eq!(r.top, OptionF32::None);
639 assert_eq!(r.right, OptionF32::None);
640 assert_eq!(r.bottom, OptionF32::None);
641 assert_eq!(r.left, OptionF32::None);
642 }
643
644 #[test]
645 fn test_parse_clip_rect_mixed_auto_and_lengths() {
646 let r = parse_clip_rect("rect(10px, auto, 30px, auto)").unwrap();
647 assert_eq!(r.top, OptionF32::Some(10.0));
648 assert_eq!(r.right, OptionF32::None);
649 assert_eq!(r.bottom, OptionF32::Some(30.0));
650 assert_eq!(r.left, OptionF32::None);
651 }
652
653 #[test]
654 fn test_parse_clip_rect_negative_lengths() {
655 let r = parse_clip_rect("rect(-5px, 0px, -10px, 0px)").unwrap();
656 assert_eq!(r.top, OptionF32::Some(-5.0));
657 assert_eq!(r.right, OptionF32::Some(0.0));
658 assert_eq!(r.bottom, OptionF32::Some(-10.0));
659 assert_eq!(r.left, OptionF32::Some(0.0));
660 }
661
662 #[test]
663 fn test_parse_clip_rect_legacy_space_separated() {
664 let r = parse_clip_rect("rect(1px 2px 3px 4px)").unwrap();
666 assert_eq!(r.top, OptionF32::Some(1.0));
667 assert_eq!(r.right, OptionF32::Some(2.0));
668 assert_eq!(r.bottom, OptionF32::Some(3.0));
669 assert_eq!(r.left, OptionF32::Some(4.0));
670 }
671
672 #[test]
673 fn test_parse_clip_rect_malformed() {
674 assert!(parse_clip_rect("").is_err());
675 assert!(parse_clip_rect("none").is_err());
676 assert!(parse_clip_rect("rect(10px, 20px, 30px)").is_err());
678 assert!(parse_clip_rect("rect(10px, 20px, 30px, 40px").is_err());
680 assert!(parse_clip_rect("rect(10px, abc, 30px, 40px)").is_err());
682 }
683}