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    pub fn creates_block_context(&self) -> bool {
64        matches!(
65            self,
66            LayoutDisplay::Block
67                | LayoutDisplay::FlowRoot
68                | LayoutDisplay::Flex
69                | LayoutDisplay::Grid
70                | LayoutDisplay::Table
71                | LayoutDisplay::ListItem
72        )
73    }
74
75    /// Returns true if this display type establishes a flex formatting context.
76    pub fn creates_flex_context(&self) -> bool {
77        matches!(self, LayoutDisplay::Flex | LayoutDisplay::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    pub fn creates_table_context(&self) -> bool {
83        matches!(self, LayoutDisplay::Table | LayoutDisplay::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    pub fn is_layout_internal(&self) -> bool {
90        matches!(
91            self,
92            LayoutDisplay::TableRowGroup
93                | LayoutDisplay::TableHeaderGroup
94                | LayoutDisplay::TableFooterGroup
95                | LayoutDisplay::TableRow
96                | LayoutDisplay::TableColumnGroup
97                | LayoutDisplay::TableColumn
98                | LayoutDisplay::TableCell
99                | LayoutDisplay::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    pub fn is_inline_level(&self) -> bool {
108        matches!(
109            self,
110            LayoutDisplay::Inline
111                | LayoutDisplay::InlineBlock
112                | LayoutDisplay::InlineFlex
113                | LayoutDisplay::InlineTable
114                | LayoutDisplay::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            LayoutDisplay::None => "none",
124            LayoutDisplay::Block => "block",
125            LayoutDisplay::Inline => "inline",
126            LayoutDisplay::InlineBlock => "inline-block",
127            LayoutDisplay::Flex => "flex",
128            LayoutDisplay::InlineFlex => "inline-flex",
129            LayoutDisplay::Table => "table",
130            LayoutDisplay::InlineTable => "inline-table",
131            LayoutDisplay::TableRowGroup => "table-row-group",
132            LayoutDisplay::TableHeaderGroup => "table-header-group",
133            LayoutDisplay::TableFooterGroup => "table-footer-group",
134            LayoutDisplay::TableRow => "table-row",
135            LayoutDisplay::TableColumnGroup => "table-column-group",
136            LayoutDisplay::TableColumn => "table-column",
137            LayoutDisplay::TableCell => "table-cell",
138            LayoutDisplay::TableCaption => "table-caption",
139            LayoutDisplay::ListItem => "list-item",
140            LayoutDisplay::RunIn => "run-in",
141            LayoutDisplay::Marker => "marker",
142            LayoutDisplay::FlowRoot => "flow-root",
143            LayoutDisplay::Grid => "grid",
144            LayoutDisplay::InlineGrid => "inline-grid",
145            LayoutDisplay::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            LayoutFloat::Left => "left",
164            LayoutFloat::Right => "right",
165            LayoutFloat::None => "none",
166        })
167    }
168}
169
170// --- PARSERS ---
171
172#[cfg(feature = "parser")]
173#[derive(Clone, PartialEq)]
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)]
188#[repr(C, u8)]
189pub enum LayoutDisplayParseErrorOwned {
190    InvalidValue(AzString),
191}
192
193#[cfg(feature = "parser")]
194impl<'a> LayoutDisplayParseError<'a> {
195    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    pub fn to_shared<'a>(&'a self) -> LayoutDisplayParseError<'a> {
205        match self {
206            Self::InvalidValue(s) => LayoutDisplayParseError::InvalidValue(s.as_str()),
207        }
208    }
209}
210
211#[cfg(feature = "parser")]
212pub fn parse_layout_display<'a>(
213    input: &'a str,
214) -> Result<LayoutDisplay, LayoutDisplayParseError<'a>> {
215    let input = input.trim();
216    match input {
217        "none" => Ok(LayoutDisplay::None),
218        "block" => Ok(LayoutDisplay::Block),
219        "inline" => Ok(LayoutDisplay::Inline),
220        // +spec:display-property:f704ef - legacy single-keyword inline-level display values (inline-block, inline-table, inline-flex, inline-grid)
221        "inline-block" => Ok(LayoutDisplay::InlineBlock),
222        "flex" => Ok(LayoutDisplay::Flex),
223        "inline-flex" => Ok(LayoutDisplay::InlineFlex),
224        "table" => Ok(LayoutDisplay::Table),
225        "inline-table" => Ok(LayoutDisplay::InlineTable),
226        "table-row-group" => Ok(LayoutDisplay::TableRowGroup),
227        "table-header-group" => Ok(LayoutDisplay::TableHeaderGroup),
228        "table-footer-group" => Ok(LayoutDisplay::TableFooterGroup),
229        "table-row" => Ok(LayoutDisplay::TableRow),
230        "table-column-group" => Ok(LayoutDisplay::TableColumnGroup),
231        "table-column" => Ok(LayoutDisplay::TableColumn),
232        "table-cell" => Ok(LayoutDisplay::TableCell),
233        "table-caption" => Ok(LayoutDisplay::TableCaption),
234        "list-item" => Ok(LayoutDisplay::ListItem),
235        "run-in" => Ok(LayoutDisplay::RunIn),
236        "marker" => Ok(LayoutDisplay::Marker),
237        "grid" => Ok(LayoutDisplay::Grid),
238        "inline-grid" => Ok(LayoutDisplay::InlineGrid),
239        "flow-root" => Ok(LayoutDisplay::FlowRoot),
240        "contents" => Ok(LayoutDisplay::Contents),
241        _ => Err(LayoutDisplayParseError::InvalidValue(input)),
242    }
243}
244
245#[cfg(feature = "parser")]
246#[derive(Clone, PartialEq)]
247pub enum LayoutFloatParseError<'a> {
248    InvalidValue(&'a str),
249}
250
251#[cfg(feature = "parser")]
252impl_debug_as_display!(LayoutFloatParseError<'a>);
253
254#[cfg(feature = "parser")]
255impl_display! { LayoutFloatParseError<'a>, {
256    InvalidValue(val) => format!("Invalid float value: \"{}\"", val),
257}}
258
259#[cfg(feature = "parser")]
260#[derive(Debug, Clone, PartialEq)]
261#[repr(C, u8)]
262pub enum LayoutFloatParseErrorOwned {
263    InvalidValue(AzString),
264}
265
266#[cfg(feature = "parser")]
267impl<'a> LayoutFloatParseError<'a> {
268    pub fn to_contained(&self) -> LayoutFloatParseErrorOwned {
269        match self {
270            Self::InvalidValue(s) => LayoutFloatParseErrorOwned::InvalidValue(s.to_string().into()),
271        }
272    }
273}
274
275#[cfg(feature = "parser")]
276impl LayoutFloatParseErrorOwned {
277    pub fn to_shared<'a>(&'a self) -> LayoutFloatParseError<'a> {
278        match self {
279            Self::InvalidValue(s) => LayoutFloatParseError::InvalidValue(s.as_str()),
280        }
281    }
282}
283
284#[cfg(feature = "parser")]
285pub fn parse_layout_float<'a>(input: &'a str) -> Result<LayoutFloat, LayoutFloatParseError<'a>> {
286    let input = input.trim();
287    match input {
288        "left" => Ok(LayoutFloat::Left),
289        "right" => Ok(LayoutFloat::Right),
290        "none" => Ok(LayoutFloat::None),
291        _ => Err(LayoutFloatParseError::InvalidValue(input)),
292    }
293}
294
295#[cfg(all(test, feature = "parser"))]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_parse_layout_display() {
301        assert_eq!(parse_layout_display("block").unwrap(), LayoutDisplay::Block);
302        assert_eq!(
303            parse_layout_display("inline").unwrap(),
304            LayoutDisplay::Inline
305        );
306        assert_eq!(
307            parse_layout_display("inline-block").unwrap(),
308            LayoutDisplay::InlineBlock
309        );
310        assert_eq!(parse_layout_display("flex").unwrap(), LayoutDisplay::Flex);
311        assert_eq!(
312            parse_layout_display("inline-flex").unwrap(),
313            LayoutDisplay::InlineFlex
314        );
315        assert_eq!(parse_layout_display("grid").unwrap(), LayoutDisplay::Grid);
316        assert_eq!(
317            parse_layout_display("inline-grid").unwrap(),
318            LayoutDisplay::InlineGrid
319        );
320        assert_eq!(parse_layout_display("none").unwrap(), LayoutDisplay::None);
321        assert_eq!(
322            parse_layout_display("flow-root").unwrap(),
323            LayoutDisplay::FlowRoot
324        );
325        assert_eq!(
326            parse_layout_display("list-item").unwrap(),
327            LayoutDisplay::ListItem
328        );
329        // Note: 'inherit' and 'initial' are handled by the CSS cascade system,
330        // not as enum variants
331        assert!(parse_layout_display("inherit").is_err());
332        assert!(parse_layout_display("initial").is_err());
333
334        // Table values
335        assert_eq!(parse_layout_display("table").unwrap(), LayoutDisplay::Table);
336        assert_eq!(
337            parse_layout_display("inline-table").unwrap(),
338            LayoutDisplay::InlineTable
339        );
340        assert_eq!(
341            parse_layout_display("table-row").unwrap(),
342            LayoutDisplay::TableRow
343        );
344        assert_eq!(
345            parse_layout_display("table-cell").unwrap(),
346            LayoutDisplay::TableCell
347        );
348        assert_eq!(
349            parse_layout_display("table-caption").unwrap(),
350            LayoutDisplay::TableCaption
351        );
352        assert_eq!(
353            parse_layout_display("table-column-group").unwrap(),
354            LayoutDisplay::TableColumnGroup
355        );
356        assert_eq!(
357            parse_layout_display("table-header-group").unwrap(),
358            LayoutDisplay::TableHeaderGroup
359        );
360        assert_eq!(
361            parse_layout_display("table-footer-group").unwrap(),
362            LayoutDisplay::TableFooterGroup
363        );
364        assert_eq!(
365            parse_layout_display("table-row-group").unwrap(),
366            LayoutDisplay::TableRowGroup
367        );
368
369        // Whitespace
370        assert_eq!(
371            parse_layout_display("  inline-flex  ").unwrap(),
372            LayoutDisplay::InlineFlex
373        );
374
375        // Invalid values
376        assert!(parse_layout_display("invalid-value").is_err());
377        assert!(parse_layout_display("").is_err());
378        assert!(parse_layout_display("display").is_err());
379    }
380
381    #[test]
382    fn test_parse_layout_float() {
383        assert_eq!(parse_layout_float("left").unwrap(), LayoutFloat::Left);
384        assert_eq!(parse_layout_float("right").unwrap(), LayoutFloat::Right);
385        assert_eq!(parse_layout_float("none").unwrap(), LayoutFloat::None);
386
387        // Whitespace
388        assert_eq!(parse_layout_float("  right  ").unwrap(), LayoutFloat::Right);
389
390        // Invalid values
391        assert!(parse_layout_float("center").is_err());
392        assert!(parse_layout_float("").is_err());
393        assert!(parse_layout_float("float-left").is_err());
394    }
395}