use alloc::string::{String, ToString};
use crate::corety::{AzString, OptionF32};
use crate::props::formatter::PrintAsCssValue;
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum LayoutOverflow {
Scroll,
Auto,
Hidden,
#[default]
Visible,
Clip,
}
impl LayoutOverflow {
pub fn needs_scrollbar(&self, currently_overflowing: bool) -> bool {
match self {
LayoutOverflow::Scroll => true,
LayoutOverflow::Auto => currently_overflowing,
LayoutOverflow::Hidden | LayoutOverflow::Visible | LayoutOverflow::Clip => false,
}
}
pub fn is_clipped(&self) -> bool {
matches!(
self,
LayoutOverflow::Hidden
| LayoutOverflow::Clip
| LayoutOverflow::Auto
| LayoutOverflow::Scroll
)
}
pub fn is_scroll(&self) -> bool {
matches!(self, LayoutOverflow::Scroll)
}
pub fn is_overflow_visible(&self) -> bool {
*self == LayoutOverflow::Visible
}
pub fn is_overflow_hidden(&self) -> bool {
*self == LayoutOverflow::Hidden
}
pub fn resolve_computed(self, other_axis: LayoutOverflow) -> LayoutOverflow {
let other_is_scrollable = !matches!(other_axis, LayoutOverflow::Visible | LayoutOverflow::Clip);
if other_is_scrollable {
match self {
LayoutOverflow::Visible => LayoutOverflow::Auto,
LayoutOverflow::Clip => LayoutOverflow::Hidden,
other => other,
}
} else {
self
}
}
}
impl PrintAsCssValue for LayoutOverflow {
fn print_as_css_value(&self) -> String {
String::from(match self {
LayoutOverflow::Scroll => "scroll",
LayoutOverflow::Auto => "auto",
LayoutOverflow::Hidden => "hidden",
LayoutOverflow::Visible => "visible",
LayoutOverflow::Clip => "clip",
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum LayoutOverflowParseError<'a> {
InvalidValue(&'a str),
}
impl_debug_as_display!(LayoutOverflowParseError<'a>);
impl_display! { LayoutOverflowParseError<'a>, {
InvalidValue(val) => format!(
"Invalid overflow value: \"{}\". Expected 'scroll', 'auto', 'hidden', 'visible', or 'clip'.", val
),
}}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum LayoutOverflowParseErrorOwned {
InvalidValue(AzString),
}
impl<'a> LayoutOverflowParseError<'a> {
pub fn to_contained(&self) -> LayoutOverflowParseErrorOwned {
match self {
LayoutOverflowParseError::InvalidValue(s) => {
LayoutOverflowParseErrorOwned::InvalidValue(s.to_string().into())
}
}
}
}
impl LayoutOverflowParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> LayoutOverflowParseError<'a> {
match self {
LayoutOverflowParseErrorOwned::InvalidValue(s) => {
LayoutOverflowParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
pub fn parse_layout_overflow<'a>(
input: &'a str,
) -> Result<LayoutOverflow, LayoutOverflowParseError<'a>> {
let input_trimmed = input.trim();
match input_trimmed {
"scroll" => Ok(LayoutOverflow::Scroll),
"auto" | "overlay" => Ok(LayoutOverflow::Auto), "hidden" => Ok(LayoutOverflow::Hidden),
"visible" => Ok(LayoutOverflow::Visible),
"clip" => Ok(LayoutOverflow::Clip),
_ => Err(LayoutOverflowParseError::InvalidValue(input)),
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum StyleScrollbarGutter {
#[default]
Auto,
Stable,
StableBothEdges,
}
impl PrintAsCssValue for StyleScrollbarGutter {
fn print_as_css_value(&self) -> String {
String::from(match self {
StyleScrollbarGutter::Auto => "auto",
StyleScrollbarGutter::Stable => "stable",
StyleScrollbarGutter::StableBothEdges => "stable both-edges",
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum StyleScrollbarGutterParseError<'a> {
InvalidValue(&'a str),
}
impl_debug_as_display!(StyleScrollbarGutterParseError<'a>);
impl_display! { StyleScrollbarGutterParseError<'a>, {
InvalidValue(val) => format!(
"Invalid scrollbar-gutter value: \"{}\". Expected 'auto', 'stable', or 'stable both-edges'.", val
),
}}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum StyleScrollbarGutterParseErrorOwned {
InvalidValue(AzString),
}
impl<'a> StyleScrollbarGutterParseError<'a> {
pub fn to_contained(&self) -> StyleScrollbarGutterParseErrorOwned {
match self {
StyleScrollbarGutterParseError::InvalidValue(s) => {
StyleScrollbarGutterParseErrorOwned::InvalidValue(s.to_string().into())
}
}
}
}
impl StyleScrollbarGutterParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> StyleScrollbarGutterParseError<'a> {
match self {
StyleScrollbarGutterParseErrorOwned::InvalidValue(s) => {
StyleScrollbarGutterParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
pub fn parse_style_scrollbar_gutter<'a>(
input: &'a str,
) -> Result<StyleScrollbarGutter, StyleScrollbarGutterParseError<'a>> {
let input_trimmed = input.trim();
match input_trimmed {
"auto" => Ok(StyleScrollbarGutter::Auto),
"stable" => Ok(StyleScrollbarGutter::Stable),
"stable both-edges" => Ok(StyleScrollbarGutter::StableBothEdges),
_ => Err(StyleScrollbarGutterParseError::InvalidValue(input)),
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum VisualBox {
ContentBox,
#[default]
PaddingBox,
BorderBox,
}
impl PrintAsCssValue for VisualBox {
fn print_as_css_value(&self) -> String {
String::from(match self {
VisualBox::ContentBox => "content-box",
VisualBox::PaddingBox => "padding-box",
VisualBox::BorderBox => "border-box",
})
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct StyleOverflowClipMargin {
pub clip_edge: VisualBox,
pub inner: crate::props::basic::pixel::PixelValue,
}
impl PrintAsCssValue for StyleOverflowClipMargin {
fn print_as_css_value(&self) -> String {
let edge = self.clip_edge.print_as_css_value();
let len = self.inner.print_as_css_value();
#[allow(clippy::float_cmp)] if self.inner.number.get() == 0.0 {
edge
} else if self.clip_edge == VisualBox::PaddingBox {
len
} else {
format!("{} {}", edge, len)
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum StyleOverflowClipMarginParseError<'a> {
InvalidValue(&'a str),
}
impl_debug_as_display!(StyleOverflowClipMarginParseError<'a>);
impl_display! { StyleOverflowClipMarginParseError<'a>, {
InvalidValue(val) => format!("Invalid overflow-clip-margin value: \"{}\"", val),
}}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum StyleOverflowClipMarginParseErrorOwned {
InvalidValue(AzString),
}
impl<'a> StyleOverflowClipMarginParseError<'a> {
pub fn to_contained(&self) -> StyleOverflowClipMarginParseErrorOwned {
match self {
StyleOverflowClipMarginParseError::InvalidValue(s) => {
StyleOverflowClipMarginParseErrorOwned::InvalidValue(s.to_string().into())
}
}
}
}
impl StyleOverflowClipMarginParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> StyleOverflowClipMarginParseError<'a> {
match self {
StyleOverflowClipMarginParseErrorOwned::InvalidValue(s) => {
StyleOverflowClipMarginParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
pub fn parse_style_overflow_clip_margin<'a>(
input: &'a str,
) -> Result<StyleOverflowClipMargin, StyleOverflowClipMarginParseError<'a>> {
use crate::props::basic::pixel::parse_pixel_value;
let input_trimmed = input.trim();
let mut clip_edge = None;
let mut length = None;
for token in input_trimmed.split_whitespace() {
match token {
"content-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::ContentBox),
"padding-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::PaddingBox),
"border-box" if clip_edge.is_none() => clip_edge = Some(VisualBox::BorderBox),
_ if length.is_none() => {
match parse_pixel_value(token) {
Ok(pv) => length = Some(pv),
Err(_) => return Err(StyleOverflowClipMarginParseError::InvalidValue(input)),
}
}
_ => return Err(StyleOverflowClipMarginParseError::InvalidValue(input)),
}
}
if clip_edge.is_none() && length.is_none() {
return Err(StyleOverflowClipMarginParseError::InvalidValue(input));
}
Ok(StyleOverflowClipMargin {
clip_edge: clip_edge.unwrap_or_default(),
inner: length.unwrap_or_default(),
})
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct StyleClipRect {
pub top: OptionF32,
pub right: OptionF32,
pub bottom: OptionF32,
pub left: OptionF32,
}
impl StyleClipRect {
pub fn resolve(
&self,
used_width: f32,
used_height: f32,
padding_left: f32,
padding_right: f32,
padding_top: f32,
padding_bottom: f32,
border_left: f32,
border_right: f32,
border_top: f32,
border_bottom: f32,
) -> (f32, f32, f32, f32) {
let top = self.top.into_option().unwrap_or(0.0);
let left = self.left.into_option().unwrap_or(0.0);
let bottom = self
.bottom
.into_option()
.unwrap_or(used_height + padding_top + padding_bottom + border_top + border_bottom);
let right = self
.right
.into_option()
.unwrap_or(used_width + padding_left + padding_right + border_left + border_right);
(top, right, bottom, left)
}
}
impl PrintAsCssValue for StyleClipRect {
fn print_as_css_value(&self) -> String {
fn fmt_edge(o: &OptionF32) -> String {
match o.into_option() {
Some(v) => format!("{}px", v),
None => String::from("auto"),
}
}
format!(
"rect({}, {}, {}, {})",
fmt_edge(&self.top),
fmt_edge(&self.right),
fmt_edge(&self.bottom),
fmt_edge(&self.left)
)
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum StyleClipRectParseError<'a> {
InvalidValue(&'a str),
}
impl_debug_as_display!(StyleClipRectParseError<'a>);
impl_display! { StyleClipRectParseError<'a>, {
InvalidValue(val) => format!(
"Invalid clip value: \"{}\". Expected 'auto' or 'rect(<top>, <right>, <bottom>, <left>)'.", val
),
}}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C, u8)]
pub enum StyleClipRectParseErrorOwned {
InvalidValue(AzString),
}
impl<'a> StyleClipRectParseError<'a> {
pub fn to_contained(&self) -> StyleClipRectParseErrorOwned {
match self {
StyleClipRectParseError::InvalidValue(s) => {
StyleClipRectParseErrorOwned::InvalidValue(s.to_string().into())
}
}
}
}
impl StyleClipRectParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> StyleClipRectParseError<'a> {
match self {
StyleClipRectParseErrorOwned::InvalidValue(s) => {
StyleClipRectParseError::InvalidValue(s.as_str())
}
}
}
}
#[cfg(feature = "parser")]
fn parse_clip_edge<'a>(token: &'a str) -> Result<OptionF32, StyleClipRectParseError<'a>> {
use crate::props::basic::pixel::parse_pixel_value;
let token = token.trim();
if token.eq_ignore_ascii_case("auto") {
return Ok(OptionF32::None);
}
let pv = parse_pixel_value(token)
.map_err(|_| StyleClipRectParseError::InvalidValue(token))?;
Ok(OptionF32::Some(pv.number.get()))
}
#[cfg(feature = "parser")]
pub fn parse_clip_rect<'a>(input: &'a str) -> Result<StyleClipRect, StyleClipRectParseError<'a>> {
let trimmed = input.trim();
if trimmed.eq_ignore_ascii_case("auto") {
return Ok(StyleClipRect::default());
}
let inner = trimmed
.strip_prefix("rect(")
.or_else(|| trimmed.strip_prefix("RECT("))
.and_then(|s| s.strip_suffix(')'))
.ok_or(StyleClipRectParseError::InvalidValue(input))?;
let inner = inner.trim();
let parts: alloc::vec::Vec<&str> = if inner.contains(',') {
inner.split(',').map(|s| s.trim()).collect()
} else {
inner.split_whitespace().collect()
};
if parts.len() != 4 {
return Err(StyleClipRectParseError::InvalidValue(input));
}
Ok(StyleClipRect {
top: parse_clip_edge(parts[0])?,
right: parse_clip_edge(parts[1])?,
bottom: parse_clip_edge(parts[2])?,
left: parse_clip_edge(parts[3])?,
})
}
#[cfg(all(test, feature = "parser"))]
mod tests {
use super::*;
#[test]
fn test_parse_layout_overflow_valid() {
assert_eq!(
parse_layout_overflow("visible").unwrap(),
LayoutOverflow::Visible
);
assert_eq!(
parse_layout_overflow("hidden").unwrap(),
LayoutOverflow::Hidden
);
assert_eq!(parse_layout_overflow("clip").unwrap(), LayoutOverflow::Clip);
assert_eq!(
parse_layout_overflow("scroll").unwrap(),
LayoutOverflow::Scroll
);
assert_eq!(parse_layout_overflow("auto").unwrap(), LayoutOverflow::Auto);
}
#[test]
fn test_parse_layout_overflow_whitespace() {
assert_eq!(
parse_layout_overflow(" scroll ").unwrap(),
LayoutOverflow::Scroll
);
}
#[test]
fn test_parse_layout_overflow_invalid() {
assert!(parse_layout_overflow("none").is_err());
assert!(parse_layout_overflow("").is_err());
assert!(parse_layout_overflow("auto scroll").is_err());
assert!(parse_layout_overflow("hidden-x").is_err());
}
#[test]
fn test_needs_scrollbar() {
assert!(LayoutOverflow::Scroll.needs_scrollbar(false));
assert!(LayoutOverflow::Scroll.needs_scrollbar(true));
assert!(LayoutOverflow::Auto.needs_scrollbar(true));
assert!(!LayoutOverflow::Auto.needs_scrollbar(false));
assert!(!LayoutOverflow::Hidden.needs_scrollbar(true));
assert!(!LayoutOverflow::Visible.needs_scrollbar(true));
assert!(!LayoutOverflow::Clip.needs_scrollbar(true));
}
#[test]
fn test_parse_clip_rect_auto_keyword() {
let r = parse_clip_rect("auto").unwrap();
assert_eq!(r.top, OptionF32::None);
assert_eq!(r.right, OptionF32::None);
assert_eq!(r.bottom, OptionF32::None);
assert_eq!(r.left, OptionF32::None);
}
#[test]
fn test_parse_clip_rect_all_auto_in_rect() {
let r = parse_clip_rect("rect(auto, auto, auto, auto)").unwrap();
assert_eq!(r.top, OptionF32::None);
assert_eq!(r.right, OptionF32::None);
assert_eq!(r.bottom, OptionF32::None);
assert_eq!(r.left, OptionF32::None);
}
#[test]
fn test_parse_clip_rect_mixed_auto_and_lengths() {
let r = parse_clip_rect("rect(10px, auto, 30px, auto)").unwrap();
assert_eq!(r.top, OptionF32::Some(10.0));
assert_eq!(r.right, OptionF32::None);
assert_eq!(r.bottom, OptionF32::Some(30.0));
assert_eq!(r.left, OptionF32::None);
}
#[test]
fn test_parse_clip_rect_negative_lengths() {
let r = parse_clip_rect("rect(-5px, 0px, -10px, 0px)").unwrap();
assert_eq!(r.top, OptionF32::Some(-5.0));
assert_eq!(r.right, OptionF32::Some(0.0));
assert_eq!(r.bottom, OptionF32::Some(-10.0));
assert_eq!(r.left, OptionF32::Some(0.0));
}
#[test]
fn test_parse_clip_rect_legacy_space_separated() {
let r = parse_clip_rect("rect(1px 2px 3px 4px)").unwrap();
assert_eq!(r.top, OptionF32::Some(1.0));
assert_eq!(r.right, OptionF32::Some(2.0));
assert_eq!(r.bottom, OptionF32::Some(3.0));
assert_eq!(r.left, OptionF32::Some(4.0));
}
#[test]
fn test_parse_clip_rect_malformed() {
assert!(parse_clip_rect("").is_err());
assert!(parse_clip_rect("none").is_err());
assert!(parse_clip_rect("rect(10px, 20px, 30px)").is_err());
assert!(parse_clip_rect("rect(10px, 20px, 30px, 40px").is_err());
assert!(parse_clip_rect("rect(10px, abc, 30px, 40px)").is_err());
}
}