Skip to main content

azul_css/props/layout/
display.rs

1//! CSS properties for `display` and `float`.
2
3use alloc::string::{String, ToString};
4use crate::corety::AzString;
5
6use crate::props::formatter::PrintAsCssValue;
7
8/// Represents a `display` CSS property value
9// +spec:display-property:472a62 - display property controls box generation types per CSS 2.2 §9.2
10// +spec:display-property:cf1820 - display type enum defining box generation qualities
11#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(C)]
13pub enum LayoutDisplay {
14    // Basic display types
15    None,
16    // +spec:display-property:7d945d - outer display defaults to block, inner defaults to flow
17    #[default]
18    Block,
19    Inline,
20    InlineBlock,
21
22    // Flex layout
23    Flex,
24    InlineFlex,
25
26    // +spec:display-property:03b26a - Table display types mapping document elements to CSS table model
27    // +spec:display-property:d40388 - layout-internal display types set both inner and outer display
28    // +spec:display-property:dcf7f5 - table display values (table, inline-table, table-row, etc.) per CSS 2.2 §17
29    // +spec:table-layout:7fdc60 - display property maps elements to table roles (CSS 2.2 §17.1)
30    // Table layout
31    // +spec:display-property:1554ad - Layout-internal display types for table layout
32    // +spec:table-layout:6cc828 - <display-internal> and <display-legacy> table display types
33    Table,
34    InlineTable,
35    TableRowGroup,
36    TableHeaderGroup,
37    TableFooterGroup,
38    TableRow,
39    TableColumnGroup,
40    TableColumn,
41    TableCell,
42    TableCaption,
43
44    FlowRoot,
45
46    // List layout
47    ListItem,
48
49    // Special displays
50    RunIn,
51    Marker,
52
53    // CSS3 additions
54    Grid,
55    InlineGrid,
56
57    // display:contents - element generates no box, children promoted to parent
58    Contents,
59}
60
61impl LayoutDisplay {
62    /// Returns true if this display type establishes a block formatting context.
63    #[must_use] pub const fn creates_block_context(&self) -> bool {
64        matches!(
65            self,
66            Self::Block
67                | Self::FlowRoot
68                | Self::Flex
69                | Self::Grid
70                | Self::Table
71                | Self::ListItem
72        )
73    }
74
75    /// Returns true if this display type establishes a flex formatting context.
76    #[must_use] pub const fn creates_flex_context(&self) -> bool {
77        matches!(self, Self::Flex | Self::InlineFlex)
78    }
79
80    // +spec:display-property:798b4f - table box establishes table formatting context (CSS 2.2 §17.4)
81    /// Returns true if this display type establishes a table formatting context.
82    #[must_use] pub const fn creates_table_context(&self) -> bool {
83        matches!(self, Self::Table | Self::InlineTable)
84    }
85
86    /// Returns true for layout-internal display types (CSS Display 3 §2.4):
87    /// table-row-group, table-header-group, table-footer-group, table-row,
88    /// table-column-group, table-column, table-cell, table-caption.
89    #[must_use] pub const fn is_layout_internal(&self) -> bool {
90        matches!(
91            self,
92            Self::TableRowGroup
93                | Self::TableHeaderGroup
94                | Self::TableFooterGroup
95                | Self::TableRow
96                | Self::TableColumnGroup
97                | Self::TableColumn
98                | Self::TableCell
99                | Self::TableCaption
100        )
101    }
102
103    // +spec:display-property:101f27 - inline-level boxes (InlineBlock, InlineFlex, etc.) vs inline boxes (Inline)
104    // +spec:display-property:18e77e - inner-only display keywords (flex, grid, table, flow-root) are not inline-level, defaulting outer display to block
105    // +spec:display-property:a43e48 - inline-table is inline-level per CSS 2.2 §17.4
106    /// Returns true if this display type generates an inline-level box.
107    #[must_use] pub const fn is_inline_level(&self) -> bool {
108        matches!(
109            self,
110            Self::Inline
111                | Self::InlineBlock
112                | Self::InlineFlex
113                | Self::InlineTable
114                | Self::InlineGrid
115        )
116    }
117}
118
119// +spec:display-property:cabaec - serialization uses short display keywords per CSSOM precedence rules
120impl PrintAsCssValue for LayoutDisplay {
121    fn print_as_css_value(&self) -> String {
122        String::from(match self {
123            Self::None => "none",
124            Self::Block => "block",
125            Self::Inline => "inline",
126            Self::InlineBlock => "inline-block",
127            Self::Flex => "flex",
128            Self::InlineFlex => "inline-flex",
129            Self::Table => "table",
130            Self::InlineTable => "inline-table",
131            Self::TableRowGroup => "table-row-group",
132            Self::TableHeaderGroup => "table-header-group",
133            Self::TableFooterGroup => "table-footer-group",
134            Self::TableRow => "table-row",
135            Self::TableColumnGroup => "table-column-group",
136            Self::TableColumn => "table-column",
137            Self::TableCell => "table-cell",
138            Self::TableCaption => "table-caption",
139            Self::ListItem => "list-item",
140            Self::RunIn => "run-in",
141            Self::Marker => "marker",
142            Self::FlowRoot => "flow-root",
143            Self::Grid => "grid",
144            Self::InlineGrid => "inline-grid",
145            Self::Contents => "contents",
146        })
147    }
148}
149
150/// Represents a `float` attribute
151#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
152#[repr(C)]
153pub enum LayoutFloat {
154    Left,
155    Right,
156    #[default]
157    None,
158}
159
160impl PrintAsCssValue for LayoutFloat {
161    fn print_as_css_value(&self) -> String {
162        String::from(match self {
163            Self::Left => "left",
164            Self::Right => "right",
165            Self::None => "none",
166        })
167    }
168}
169
170// --- PARSERS ---
171
172#[cfg(feature = "parser")]
173#[derive(Clone, PartialEq, Eq)]
174pub enum LayoutDisplayParseError<'a> {
175    InvalidValue(&'a str),
176}
177
178#[cfg(feature = "parser")]
179impl_debug_as_display!(LayoutDisplayParseError<'a>);
180
181#[cfg(feature = "parser")]
182impl_display! { LayoutDisplayParseError<'a>, {
183    InvalidValue(val) => format!("Invalid display value: \"{}\"", val),
184}}
185
186#[cfg(feature = "parser")]
187#[derive(Debug, Clone, PartialEq, Eq)]
188#[repr(C, u8)]
189pub enum LayoutDisplayParseErrorOwned {
190    InvalidValue(AzString),
191}
192
193#[cfg(feature = "parser")]
194impl LayoutDisplayParseError<'_> {
195    #[must_use] pub fn to_contained(&self) -> LayoutDisplayParseErrorOwned {
196        match self {
197            Self::InvalidValue(s) => LayoutDisplayParseErrorOwned::InvalidValue((*s).to_string().into()),
198        }
199    }
200}
201
202#[cfg(feature = "parser")]
203impl LayoutDisplayParseErrorOwned {
204    #[must_use] pub fn to_shared(&self) -> LayoutDisplayParseError<'_> {
205        match self {
206            Self::InvalidValue(s) => LayoutDisplayParseError::InvalidValue(s.as_str()),
207        }
208    }
209}
210
211#[cfg(feature = "parser")]
212/// # Errors
213///
214/// Returns an error if `input` is not a valid CSS `display` value.
215pub fn parse_layout_display(
216    input: &str,
217) -> Result<LayoutDisplay, LayoutDisplayParseError<'_>> {
218    let input = input.trim();
219    match input {
220        "none" => Ok(LayoutDisplay::None),
221        "block" => Ok(LayoutDisplay::Block),
222        "inline" => Ok(LayoutDisplay::Inline),
223        // +spec:display-property:f704ef - legacy single-keyword inline-level display values (inline-block, inline-table, inline-flex, inline-grid)
224        "inline-block" => Ok(LayoutDisplay::InlineBlock),
225        "flex" => Ok(LayoutDisplay::Flex),
226        "inline-flex" => Ok(LayoutDisplay::InlineFlex),
227        "table" => Ok(LayoutDisplay::Table),
228        "inline-table" => Ok(LayoutDisplay::InlineTable),
229        "table-row-group" => Ok(LayoutDisplay::TableRowGroup),
230        "table-header-group" => Ok(LayoutDisplay::TableHeaderGroup),
231        "table-footer-group" => Ok(LayoutDisplay::TableFooterGroup),
232        "table-row" => Ok(LayoutDisplay::TableRow),
233        "table-column-group" => Ok(LayoutDisplay::TableColumnGroup),
234        "table-column" => Ok(LayoutDisplay::TableColumn),
235        "table-cell" => Ok(LayoutDisplay::TableCell),
236        "table-caption" => Ok(LayoutDisplay::TableCaption),
237        "list-item" => Ok(LayoutDisplay::ListItem),
238        "run-in" => Ok(LayoutDisplay::RunIn),
239        "marker" => Ok(LayoutDisplay::Marker),
240        "grid" => Ok(LayoutDisplay::Grid),
241        "inline-grid" => Ok(LayoutDisplay::InlineGrid),
242        "flow-root" => Ok(LayoutDisplay::FlowRoot),
243        "contents" => Ok(LayoutDisplay::Contents),
244        _ => Err(LayoutDisplayParseError::InvalidValue(input)),
245    }
246}
247
248#[cfg(feature = "parser")]
249#[derive(Clone, PartialEq, Eq)]
250pub enum LayoutFloatParseError<'a> {
251    InvalidValue(&'a str),
252}
253
254#[cfg(feature = "parser")]
255impl_debug_as_display!(LayoutFloatParseError<'a>);
256
257#[cfg(feature = "parser")]
258impl_display! { LayoutFloatParseError<'a>, {
259    InvalidValue(val) => format!("Invalid float value: \"{}\"", val),
260}}
261
262#[cfg(feature = "parser")]
263#[derive(Debug, Clone, PartialEq, Eq)]
264#[repr(C, u8)]
265pub enum LayoutFloatParseErrorOwned {
266    InvalidValue(AzString),
267}
268
269#[cfg(feature = "parser")]
270impl LayoutFloatParseError<'_> {
271    #[must_use] pub fn to_contained(&self) -> LayoutFloatParseErrorOwned {
272        match self {
273            Self::InvalidValue(s) => LayoutFloatParseErrorOwned::InvalidValue((*s).to_string().into()),
274        }
275    }
276}
277
278#[cfg(feature = "parser")]
279impl LayoutFloatParseErrorOwned {
280    #[must_use] pub fn to_shared(&self) -> LayoutFloatParseError<'_> {
281        match self {
282            Self::InvalidValue(s) => LayoutFloatParseError::InvalidValue(s.as_str()),
283        }
284    }
285}
286
287#[cfg(feature = "parser")]
288/// # Errors
289///
290/// Returns an error if `input` is not a valid CSS `float` value.
291pub fn parse_layout_float(input: &str) -> Result<LayoutFloat, LayoutFloatParseError<'_>> {
292    let input = input.trim();
293    match input {
294        "left" => Ok(LayoutFloat::Left),
295        "right" => Ok(LayoutFloat::Right),
296        "none" => Ok(LayoutFloat::None),
297        _ => Err(LayoutFloatParseError::InvalidValue(input)),
298    }
299}
300
301#[cfg(all(test, feature = "parser"))]
302mod tests {
303    use super::*;
304
305    #[test]
306    #[allow(clippy::cognitive_complexity)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
307    fn test_parse_layout_display() {
308        assert_eq!(parse_layout_display("block").unwrap(), LayoutDisplay::Block);
309        assert_eq!(
310            parse_layout_display("inline").unwrap(),
311            LayoutDisplay::Inline
312        );
313        assert_eq!(
314            parse_layout_display("inline-block").unwrap(),
315            LayoutDisplay::InlineBlock
316        );
317        assert_eq!(parse_layout_display("flex").unwrap(), LayoutDisplay::Flex);
318        assert_eq!(
319            parse_layout_display("inline-flex").unwrap(),
320            LayoutDisplay::InlineFlex
321        );
322        assert_eq!(parse_layout_display("grid").unwrap(), LayoutDisplay::Grid);
323        assert_eq!(
324            parse_layout_display("inline-grid").unwrap(),
325            LayoutDisplay::InlineGrid
326        );
327        assert_eq!(parse_layout_display("none").unwrap(), LayoutDisplay::None);
328        assert_eq!(
329            parse_layout_display("flow-root").unwrap(),
330            LayoutDisplay::FlowRoot
331        );
332        assert_eq!(
333            parse_layout_display("list-item").unwrap(),
334            LayoutDisplay::ListItem
335        );
336        // Note: 'inherit' and 'initial' are handled by the CSS cascade system,
337        // not as enum variants
338        assert!(parse_layout_display("inherit").is_err());
339        assert!(parse_layout_display("initial").is_err());
340
341        // Table values
342        assert_eq!(parse_layout_display("table").unwrap(), LayoutDisplay::Table);
343        assert_eq!(
344            parse_layout_display("inline-table").unwrap(),
345            LayoutDisplay::InlineTable
346        );
347        assert_eq!(
348            parse_layout_display("table-row").unwrap(),
349            LayoutDisplay::TableRow
350        );
351        assert_eq!(
352            parse_layout_display("table-cell").unwrap(),
353            LayoutDisplay::TableCell
354        );
355        assert_eq!(
356            parse_layout_display("table-caption").unwrap(),
357            LayoutDisplay::TableCaption
358        );
359        assert_eq!(
360            parse_layout_display("table-column-group").unwrap(),
361            LayoutDisplay::TableColumnGroup
362        );
363        assert_eq!(
364            parse_layout_display("table-header-group").unwrap(),
365            LayoutDisplay::TableHeaderGroup
366        );
367        assert_eq!(
368            parse_layout_display("table-footer-group").unwrap(),
369            LayoutDisplay::TableFooterGroup
370        );
371        assert_eq!(
372            parse_layout_display("table-row-group").unwrap(),
373            LayoutDisplay::TableRowGroup
374        );
375
376        // Whitespace
377        assert_eq!(
378            parse_layout_display("  inline-flex  ").unwrap(),
379            LayoutDisplay::InlineFlex
380        );
381
382        // Invalid values
383        assert!(parse_layout_display("invalid-value").is_err());
384        assert!(parse_layout_display("").is_err());
385        assert!(parse_layout_display("display").is_err());
386    }
387
388    #[test]
389    fn test_parse_layout_float() {
390        assert_eq!(parse_layout_float("left").unwrap(), LayoutFloat::Left);
391        assert_eq!(parse_layout_float("right").unwrap(), LayoutFloat::Right);
392        assert_eq!(parse_layout_float("none").unwrap(), LayoutFloat::None);
393
394        // Whitespace
395        assert_eq!(parse_layout_float("  right  ").unwrap(), LayoutFloat::Right);
396
397        // Invalid values
398        assert!(parse_layout_float("center").is_err());
399        assert!(parse_layout_float("").is_err());
400        assert!(parse_layout_float("float-left").is_err());
401    }
402}