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
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
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::format_rust_code::FormatAsRustCode for FlowInto {
57    fn format_as_rust_code(&self, _tabs: usize) -> String {
58        match self {
59            FlowInto::None => String::from("FlowInto::None"),
60            FlowInto::Named(s) => format!(
61                "FlowInto::Named(AzString::from_const_str({:?}))",
62                s.as_str()
63            ),
64        }
65    }
66}
67
68impl crate::format_rust_code::FormatAsRustCode for FlowFrom {
69    fn format_as_rust_code(&self, _tabs: usize) -> String {
70        match self {
71            FlowFrom::None => String::from("FlowFrom::None"),
72            FlowFrom::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    use super::*;
85    use crate::corety::AzString;
86
87    macro_rules! define_flow_parser {
88        (
89            $fn_name:ident,
90            $struct_name:ident,
91            $error_name:ident,
92            $error_owned_name:ident,
93            $prop_name:expr
94        ) => {
95            #[derive(Clone, PartialEq)]
96            pub enum $error_name<'a> {
97                InvalidValue(&'a str),
98            }
99
100            impl_debug_as_display!($error_name<'a>);
101            impl_display! { $error_name<'a>, {
102                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
103            }}
104
105            #[derive(Debug, Clone, PartialEq)]
106            #[repr(C, u8)]
107            pub enum $error_owned_name {
108                InvalidValue(AzString),
109            }
110
111            impl<'a> $error_name<'a> {
112                pub fn to_contained(&self) -> $error_owned_name {
113                    match self {
114                        Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string().into()),
115                    }
116                }
117            }
118
119            impl $error_owned_name {
120                pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
121                    match self {
122                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
123                    }
124                }
125            }
126
127            pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
128                let trimmed = input.trim();
129                if trimmed.is_empty() {
130                    return Err($error_name::InvalidValue(input));
131                }
132                match trimmed {
133                    "none" => Ok($struct_name::None),
134                    // any other value is a custom identifier
135                    ident => Ok($struct_name::Named(ident.to_string().into())),
136                }
137            }
138        };
139    }
140
141    define_flow_parser!(
142        parse_flow_into,
143        FlowInto,
144        FlowIntoParseError,
145        FlowIntoParseErrorOwned,
146        "flow-into"
147    );
148    define_flow_parser!(
149        parse_flow_from,
150        FlowFrom,
151        FlowFromParseError,
152        FlowFromParseErrorOwned,
153        "flow-from"
154    );
155}
156
157#[cfg(feature = "parser")]
158pub use parser::*;
159
160#[cfg(all(test, feature = "parser"))]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_parse_flow_into() {
166        assert_eq!(parse_flow_into("none").unwrap(), FlowInto::None);
167        assert_eq!(
168            parse_flow_into("my-article-flow").unwrap(),
169            FlowInto::Named("my-article-flow".into())
170        );
171        assert!(parse_flow_into("").is_err());
172    }
173
174    #[test]
175    fn test_parse_flow_from() {
176        assert_eq!(parse_flow_from("none").unwrap(), FlowFrom::None);
177        assert_eq!(
178            parse_flow_from("  main-thread  ").unwrap(),
179            FlowFrom::Named("main-thread".into())
180        );
181        assert!(parse_flow_from("").is_err());
182    }
183}