Skip to main content

azul_css/props/layout/
flow.rs

1//! CSS properties for flowing content into regions (`flow-into`, `flow-from`).
2
3use alloc::string::{String, ToString};
4
5use crate::{corety::AzString, props::formatter::PrintAsCssValue};
6
7// --- flow-into ---
8#[allow(variant_size_differences)]
9// repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
10/// CSS `flow-into` property — diverts an element's content into a named flow.
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(C, u8)]
13#[derive(Default)]
14pub enum FlowInto {
15    /// Content is not diverted into any named flow (default).
16    #[default]
17    None,
18    /// Content is diverted into the named flow identified by this string.
19    Named(AzString),
20}
21
22impl PrintAsCssValue for FlowInto {
23    fn print_as_css_value(&self) -> String {
24        match self {
25            Self::None => "none".to_string(),
26            Self::Named(s) => s.to_string(),
27        }
28    }
29}
30
31// --- flow-from ---
32#[allow(variant_size_differences)]
33// repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
34/// CSS `flow-from` property — consumes content from a named flow into a region.
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[repr(C, u8)]
37#[derive(Default)]
38pub enum FlowFrom {
39    /// No named flow is consumed (default).
40    #[default]
41    None,
42    /// Content is consumed from the named flow identified by this string.
43    Named(AzString),
44}
45
46impl PrintAsCssValue for FlowFrom {
47    fn print_as_css_value(&self) -> String {
48        match self {
49            Self::None => "none".to_string(),
50            Self::Named(s) => s.to_string(),
51        }
52    }
53}
54
55// Formatting to Rust code
56impl crate::codegen::format::FormatAsRustCode for FlowInto {
57    fn format_as_rust_code(&self, _tabs: usize) -> String {
58        match self {
59            Self::None => String::from("FlowInto::None"),
60            Self::Named(s) => format!(
61                "FlowInto::Named(AzString::from_const_str({:?}))",
62                s.as_str()
63            ),
64        }
65    }
66}
67
68impl crate::codegen::format::FormatAsRustCode for FlowFrom {
69    fn format_as_rust_code(&self, _tabs: usize) -> String {
70        match self {
71            Self::None => String::from("FlowFrom::None"),
72            Self::Named(s) => format!(
73                "FlowFrom::Named(AzString::from_const_str({:?}))",
74                s.as_str()
75            ),
76        }
77    }
78}
79
80// --- PARSERS ---
81
82#[cfg(feature = "parser")]
83pub mod parser {
84    #[allow(clippy::wildcard_imports)]
85    // parser submodule reuses the parent module's value types
86    use super::*;
87    use crate::corety::AzString;
88
89    macro_rules! define_flow_parser {
90        (
91            $fn_name:ident,
92            $struct_name:ident,
93            $error_name:ident,
94            $error_owned_name:ident,
95            $prop_name:expr
96        ) => {
97            #[derive(Clone, PartialEq, Eq)]
98            pub enum $error_name<'a> {
99                InvalidValue(&'a str),
100            }
101
102            impl_debug_as_display!($error_name<'a>);
103            impl_display! { $error_name<'a>, {
104                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
105            }}
106
107            #[derive(Debug, Clone, PartialEq, Eq)]
108            #[repr(C, u8)]
109            pub enum $error_owned_name {
110                InvalidValue(AzString),
111            }
112
113            impl $error_name<'_> {
114                #[must_use]
115                pub fn to_contained(&self) -> $error_owned_name {
116                    match self {
117                        Self::InvalidValue(s) => {
118                            $error_owned_name::InvalidValue(s.to_string().into())
119                        }
120                    }
121                }
122            }
123
124            impl $error_owned_name {
125                #[must_use]
126                pub fn to_shared(&self) -> $error_name<'_> {
127                    match self {
128                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
129                    }
130                }
131            }
132
133            /// # Errors
134            ///
135            /// Returns an error if `input` is not a valid CSS value for this property.
136            pub fn $fn_name(input: &str) -> Result<$struct_name, $error_name<'_>> {
137                let trimmed = input.trim();
138                if trimmed.is_empty() {
139                    return Err($error_name::InvalidValue(input));
140                }
141                match trimmed {
142                    "none" => Ok($struct_name::None),
143                    // any other value is a custom identifier
144                    ident => Ok($struct_name::Named(ident.to_string().into())),
145                }
146            }
147        };
148    }
149
150    define_flow_parser!(
151        parse_flow_into,
152        FlowInto,
153        FlowIntoParseError,
154        FlowIntoParseErrorOwned,
155        "flow-into"
156    );
157    define_flow_parser!(
158        parse_flow_from,
159        FlowFrom,
160        FlowFromParseError,
161        FlowFromParseErrorOwned,
162        "flow-from"
163    );
164}
165
166#[cfg(feature = "parser")]
167pub use parser::*;
168
169#[cfg(all(test, feature = "parser"))]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_parse_flow_into() {
175        assert_eq!(parse_flow_into("none").unwrap(), FlowInto::None);
176        assert_eq!(
177            parse_flow_into("my-article-flow").unwrap(),
178            FlowInto::Named("my-article-flow".into())
179        );
180        assert!(parse_flow_into("").is_err());
181    }
182
183    #[test]
184    fn test_parse_flow_from() {
185        assert_eq!(parse_flow_from("none").unwrap(), FlowFrom::None);
186        assert_eq!(
187            parse_flow_from("  main-thread  ").unwrap(),
188            FlowFrom::Named("main-thread".into())
189        );
190        assert!(parse_flow_from("").is_err());
191    }
192}