Skip to main content

azul_css/props/layout/
overflow.rs

1//! CSS properties for managing content overflow.
2
3use alloc::string::{String, ToString};
4
5use crate::props::formatter::PrintAsCssValue;
6
7/// Represents an `overflow-x` or `overflow-y` property.
8///
9/// Determines what to do when content overflows an element's box.
10#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(C)]
12pub enum LayoutOverflow {
13    /// Always shows a scroll bar, overflows on scroll.
14    Scroll,
15    /// Shows a scroll bar only when content overflows.
16    Auto,
17    /// Clips overflowing content. The rest of the content will be invisible.
18    Hidden,
19    /// Content is not clipped and renders outside the element's box. This is the CSS default.
20    #[default]
21    Visible,
22    /// Similar to `hidden`, clips the content at the box's edge.
23    Clip,
24}
25
26impl LayoutOverflow {
27    /// Returns whether this overflow value requires a scrollbar to be displayed.
28    ///
29    /// - `overflow: scroll` always shows the scrollbar.
30    /// - `overflow: auto` only shows the scrollbar if the content is currently overflowing.
31    /// - `overflow: hidden`, `overflow: visible`, and `overflow: clip` do not show any scrollbars.
32    pub fn needs_scrollbar(&self, currently_overflowing: bool) -> bool {
33        match self {
34            LayoutOverflow::Scroll => true,
35            LayoutOverflow::Auto => currently_overflowing,
36            LayoutOverflow::Hidden | LayoutOverflow::Visible | LayoutOverflow::Clip => false,
37        }
38    }
39
40    pub fn is_clipped(&self) -> bool {
41        // All overflow values except 'visible' clip their content
42        matches!(
43            self,
44            LayoutOverflow::Hidden
45                | LayoutOverflow::Clip
46                | LayoutOverflow::Auto
47                | LayoutOverflow::Scroll
48        )
49    }
50
51    pub fn is_scroll(&self) -> bool {
52        matches!(self, LayoutOverflow::Scroll)
53    }
54
55    /// Returns `true` if the overflow type is `visible`, which is the only
56    /// overflow type that doesn't clip its children.
57    pub fn is_overflow_visible(&self) -> bool {
58        *self == LayoutOverflow::Visible
59    }
60
61    /// Returns `true` if the overflow type is `hidden`.
62    pub fn is_overflow_hidden(&self) -> bool {
63        *self == LayoutOverflow::Hidden
64    }
65}
66
67impl PrintAsCssValue for LayoutOverflow {
68    fn print_as_css_value(&self) -> String {
69        String::from(match self {
70            LayoutOverflow::Scroll => "scroll",
71            LayoutOverflow::Auto => "auto",
72            LayoutOverflow::Hidden => "hidden",
73            LayoutOverflow::Visible => "visible",
74            LayoutOverflow::Clip => "clip",
75        })
76    }
77}
78
79// -- Parser
80
81/// Error returned when parsing an `overflow` property fails.
82#[derive(Clone, PartialEq, Eq)]
83pub enum LayoutOverflowParseError<'a> {
84    /// The provided value is not a valid `overflow` keyword.
85    InvalidValue(&'a str),
86}
87
88impl_debug_as_display!(LayoutOverflowParseError<'a>);
89impl_display! { LayoutOverflowParseError<'a>, {
90    InvalidValue(val) => format!(
91        "Invalid overflow value: \"{}\". Expected 'scroll', 'auto', 'hidden', 'visible', or 'clip'.", val
92    ),
93}}
94
95/// An owned version of `LayoutOverflowParseError`.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum LayoutOverflowParseErrorOwned {
98    InvalidValue(String),
99}
100
101impl<'a> LayoutOverflowParseError<'a> {
102    /// Converts the borrowed error into an owned error.
103    pub fn to_contained(&self) -> LayoutOverflowParseErrorOwned {
104        match self {
105            LayoutOverflowParseError::InvalidValue(s) => {
106                LayoutOverflowParseErrorOwned::InvalidValue(s.to_string())
107            }
108        }
109    }
110}
111
112impl LayoutOverflowParseErrorOwned {
113    /// Converts the owned error back into a borrowed error.
114    pub fn to_shared<'a>(&'a self) -> LayoutOverflowParseError<'a> {
115        match self {
116            LayoutOverflowParseErrorOwned::InvalidValue(s) => {
117                LayoutOverflowParseError::InvalidValue(s.as_str())
118            }
119        }
120    }
121}
122
123#[cfg(feature = "parser")]
124/// Parses a `LayoutOverflow` from a string slice.
125pub fn parse_layout_overflow<'a>(
126    input: &'a str,
127) -> Result<LayoutOverflow, LayoutOverflowParseError<'a>> {
128    let input_trimmed = input.trim();
129    match input_trimmed {
130        "scroll" => Ok(LayoutOverflow::Scroll),
131        "auto" => Ok(LayoutOverflow::Auto),
132        "hidden" => Ok(LayoutOverflow::Hidden),
133        "visible" => Ok(LayoutOverflow::Visible),
134        "clip" => Ok(LayoutOverflow::Clip),
135        _ => Err(LayoutOverflowParseError::InvalidValue(input)),
136    }
137}
138
139#[cfg(all(test, feature = "parser"))]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_parse_layout_overflow_valid() {
145        assert_eq!(
146            parse_layout_overflow("visible").unwrap(),
147            LayoutOverflow::Visible
148        );
149        assert_eq!(
150            parse_layout_overflow("hidden").unwrap(),
151            LayoutOverflow::Hidden
152        );
153        assert_eq!(parse_layout_overflow("clip").unwrap(), LayoutOverflow::Clip);
154        assert_eq!(
155            parse_layout_overflow("scroll").unwrap(),
156            LayoutOverflow::Scroll
157        );
158        assert_eq!(parse_layout_overflow("auto").unwrap(), LayoutOverflow::Auto);
159    }
160
161    #[test]
162    fn test_parse_layout_overflow_whitespace() {
163        assert_eq!(
164            parse_layout_overflow("  scroll  ").unwrap(),
165            LayoutOverflow::Scroll
166        );
167    }
168
169    #[test]
170    fn test_parse_layout_overflow_invalid() {
171        assert!(parse_layout_overflow("none").is_err());
172        assert!(parse_layout_overflow("").is_err());
173        assert!(parse_layout_overflow("auto scroll").is_err());
174        assert!(parse_layout_overflow("hidden-x").is_err());
175    }
176
177    #[test]
178    fn test_needs_scrollbar() {
179        assert!(LayoutOverflow::Scroll.needs_scrollbar(false));
180        assert!(LayoutOverflow::Scroll.needs_scrollbar(true));
181        assert!(LayoutOverflow::Auto.needs_scrollbar(true));
182        assert!(!LayoutOverflow::Auto.needs_scrollbar(false));
183        assert!(!LayoutOverflow::Hidden.needs_scrollbar(true));
184        assert!(!LayoutOverflow::Visible.needs_scrollbar(true));
185        assert!(!LayoutOverflow::Clip.needs_scrollbar(true));
186    }
187}