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)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
9/// CSS `flow-into` property — diverts an element's content into a named flow.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(C, u8)]
12#[derive(Default)]
13pub enum FlowInto {
14    /// Content is not diverted into any named flow (default).
15    #[default]
16    None,
17    /// Content is diverted into the named flow identified by this string.
18    Named(AzString),
19}
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)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
33/// CSS `flow-from` property — consumes content from a named flow into a region.
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35#[repr(C, u8)]
36#[derive(Default)]
37pub enum FlowFrom {
38    /// No named flow is consumed (default).
39    #[default]
40    None,
41    /// Content is consumed from the named flow identified by this string.
42    Named(AzString),
43}
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)] // parser submodule reuses the parent module's value types
85    use super::*;
86    use crate::corety::AzString;
87
88    macro_rules! define_flow_parser {
89        (
90            $fn_name:ident,
91            $struct_name:ident,
92            $error_name:ident,
93            $error_owned_name:ident,
94            $prop_name:expr
95        ) => {
96            #[derive(Clone, PartialEq, Eq)]
97            pub enum $error_name<'a> {
98                InvalidValue(&'a str),
99            }
100
101            impl_debug_as_display!($error_name<'a>);
102            impl_display! { $error_name<'a>, {
103                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
104            }}
105
106            #[derive(Debug, Clone, PartialEq, Eq)]
107            #[repr(C, u8)]
108            pub enum $error_owned_name {
109                InvalidValue(AzString),
110            }
111
112            impl $error_name<'_> {
113                #[must_use] pub fn to_contained(&self) -> $error_owned_name {
114                    match self {
115                        Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string().into()),
116                    }
117                }
118            }
119
120            impl $error_owned_name {
121                #[must_use] pub fn to_shared(&self) -> $error_name<'_> {
122                    match self {
123                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
124                    }
125                }
126            }
127
128            /// # Errors
129            ///
130            /// Returns an error if `input` is not a valid CSS value for this property.
131            pub fn $fn_name(input: &str) -> Result<$struct_name, $error_name<'_>> {
132                let trimmed = input.trim();
133                if trimmed.is_empty() {
134                    return Err($error_name::InvalidValue(input));
135                }
136                match trimmed {
137                    "none" => Ok($struct_name::None),
138                    // any other value is a custom identifier
139                    ident => Ok($struct_name::Named(ident.to_string().into())),
140                }
141            }
142        };
143    }
144
145    define_flow_parser!(
146        parse_flow_into,
147        FlowInto,
148        FlowIntoParseError,
149        FlowIntoParseErrorOwned,
150        "flow-into"
151    );
152    define_flow_parser!(
153        parse_flow_from,
154        FlowFrom,
155        FlowFromParseError,
156        FlowFromParseErrorOwned,
157        "flow-from"
158    );
159}
160
161#[cfg(feature = "parser")]
162pub use parser::*;
163
164#[cfg(all(test, feature = "parser"))]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_parse_flow_into() {
170        assert_eq!(parse_flow_into("none").unwrap(), FlowInto::None);
171        assert_eq!(
172            parse_flow_into("my-article-flow").unwrap(),
173            FlowInto::Named("my-article-flow".into())
174        );
175        assert!(parse_flow_into("").is_err());
176    }
177
178    #[test]
179    fn test_parse_flow_from() {
180        assert_eq!(parse_flow_from("none").unwrap(), FlowFrom::None);
181        assert_eq!(
182            parse_flow_from("  main-thread  ").unwrap(),
183            FlowFrom::Named("main-thread".into())
184        );
185        assert!(parse_flow_from("").is_err());
186    }
187}