Skip to main content

azul_css/props/layout/
column.rs

1//! CSS properties for multi-column layout.
2
3use alloc::string::{String, ToString};
4use core::num::ParseIntError;
5
6use crate::props::{
7    basic::{
8        color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
9        pixel::{
10            parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
11        },
12    },
13    formatter::PrintAsCssValue,
14    style::border::{
15        parse_border_style, BorderStyle, CssBorderStyleParseError, CssBorderStyleParseErrorOwned,
16    },
17};
18
19// --- column-count ---
20
21#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[repr(C, u8)]
23pub enum ColumnCount {
24    Auto,
25    Integer(u32),
26}
27
28impl Default for ColumnCount {
29    fn default() -> Self {
30        Self::Auto
31    }
32}
33
34impl PrintAsCssValue for ColumnCount {
35    fn print_as_css_value(&self) -> String {
36        match self {
37            Self::Auto => "auto".to_string(),
38            Self::Integer(i) => i.to_string(),
39        }
40    }
41}
42
43// --- column-width ---
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[repr(C, u8)]
47pub enum ColumnWidth {
48    Auto,
49    Length(PixelValue),
50}
51
52impl Default for ColumnWidth {
53    fn default() -> Self {
54        Self::Auto
55    }
56}
57
58impl PrintAsCssValue for ColumnWidth {
59    fn print_as_css_value(&self) -> String {
60        match self {
61            Self::Auto => "auto".to_string(),
62            Self::Length(px) => px.print_as_css_value(),
63        }
64    }
65}
66
67// --- column-span ---
68
69#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[repr(C)]
71pub enum ColumnSpan {
72    None,
73    All,
74}
75
76impl Default for ColumnSpan {
77    fn default() -> Self {
78        Self::None
79    }
80}
81
82impl PrintAsCssValue for ColumnSpan {
83    fn print_as_css_value(&self) -> String {
84        String::from(match self {
85            Self::None => "none",
86            Self::All => "all",
87        })
88    }
89}
90
91// --- column-fill ---
92
93#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[repr(C)]
95pub enum ColumnFill {
96    Auto,
97    Balance,
98}
99
100impl Default for ColumnFill {
101    fn default() -> Self {
102        Self::Balance
103    }
104}
105
106impl PrintAsCssValue for ColumnFill {
107    fn print_as_css_value(&self) -> String {
108        String::from(match self {
109            Self::Auto => "auto",
110            Self::Balance => "balance",
111        })
112    }
113}
114
115// --- column-rule ---
116
117#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
118#[repr(C)]
119pub struct ColumnRuleWidth {
120    pub inner: PixelValue,
121}
122
123impl Default for ColumnRuleWidth {
124    fn default() -> Self {
125        Self {
126            inner: PixelValue::const_px(3),
127        }
128    }
129}
130
131impl PrintAsCssValue for ColumnRuleWidth {
132    fn print_as_css_value(&self) -> String {
133        self.inner.print_as_css_value()
134    }
135}
136
137#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138#[repr(C)]
139pub struct ColumnRuleStyle {
140    pub inner: BorderStyle,
141}
142
143impl Default for ColumnRuleStyle {
144    fn default() -> Self {
145        Self {
146            inner: BorderStyle::None,
147        }
148    }
149}
150
151impl PrintAsCssValue for ColumnRuleStyle {
152    fn print_as_css_value(&self) -> String {
153        self.inner.print_as_css_value()
154    }
155}
156
157#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
158#[repr(C)]
159pub struct ColumnRuleColor {
160    pub inner: ColorU,
161}
162
163impl Default for ColumnRuleColor {
164    fn default() -> Self {
165        Self {
166            inner: ColorU::BLACK,
167        } // should be `currentcolor`
168    }
169}
170
171impl PrintAsCssValue for ColumnRuleColor {
172    fn print_as_css_value(&self) -> String {
173        self.inner.to_hash()
174    }
175}
176
177// Formatting to Rust code
178impl crate::format_rust_code::FormatAsRustCode for ColumnCount {
179    fn format_as_rust_code(&self, _tabs: usize) -> String {
180        match self {
181            ColumnCount::Auto => String::from("ColumnCount::Auto"),
182            ColumnCount::Integer(i) => format!("ColumnCount::Integer({})", i),
183        }
184    }
185}
186
187impl crate::format_rust_code::FormatAsRustCode for ColumnWidth {
188    fn format_as_rust_code(&self, tabs: usize) -> String {
189        match self {
190            ColumnWidth::Auto => String::from("ColumnWidth::Auto"),
191            ColumnWidth::Length(px) => format!(
192                "ColumnWidth::Length({})",
193                crate::format_rust_code::format_pixel_value(px)
194            ),
195        }
196    }
197}
198
199impl crate::format_rust_code::FormatAsRustCode for ColumnSpan {
200    fn format_as_rust_code(&self, _tabs: usize) -> String {
201        match self {
202            ColumnSpan::None => String::from("ColumnSpan::None"),
203            ColumnSpan::All => String::from("ColumnSpan::All"),
204        }
205    }
206}
207
208impl crate::format_rust_code::FormatAsRustCode for ColumnFill {
209    fn format_as_rust_code(&self, _tabs: usize) -> String {
210        match self {
211            ColumnFill::Auto => String::from("ColumnFill::Auto"),
212            ColumnFill::Balance => String::from("ColumnFill::Balance"),
213        }
214    }
215}
216
217impl crate::format_rust_code::FormatAsRustCode for ColumnRuleWidth {
218    fn format_as_rust_code(&self, _tabs: usize) -> String {
219        format!(
220            "ColumnRuleWidth {{ inner: {} }}",
221            crate::format_rust_code::format_pixel_value(&self.inner)
222        )
223    }
224}
225
226impl crate::format_rust_code::FormatAsRustCode for ColumnRuleStyle {
227    fn format_as_rust_code(&self, tabs: usize) -> String {
228        format!(
229            "ColumnRuleStyle {{ inner: {} }}",
230            self.inner.format_as_rust_code(tabs)
231        )
232    }
233}
234
235impl crate::format_rust_code::FormatAsRustCode for ColumnRuleColor {
236    fn format_as_rust_code(&self, _tabs: usize) -> String {
237        format!(
238            "ColumnRuleColor {{ inner: {} }}",
239            crate::format_rust_code::format_color_value(&self.inner)
240        )
241    }
242}
243
244// --- PARSERS ---
245
246#[cfg(feature = "parser")]
247mod parser {
248    use super::*;
249
250    // -- ColumnCount parser
251
252    #[derive(Clone, PartialEq)]
253    pub enum ColumnCountParseError<'a> {
254        InvalidValue(&'a str),
255        ParseInt(ParseIntError),
256    }
257
258    impl_debug_as_display!(ColumnCountParseError<'a>);
259    impl_display! { ColumnCountParseError<'a>, {
260        InvalidValue(v) => format!("Invalid column-count value: \"{}\"", v),
261        ParseInt(e) => format!("Invalid integer for column-count: {}", e),
262    }}
263
264    #[derive(Debug, Clone, PartialEq)]
265    pub enum ColumnCountParseErrorOwned {
266        InvalidValue(String),
267        ParseInt(String),
268    }
269
270    impl<'a> ColumnCountParseError<'a> {
271        pub fn to_contained(&self) -> ColumnCountParseErrorOwned {
272            match self {
273                Self::InvalidValue(s) => ColumnCountParseErrorOwned::InvalidValue(s.to_string()),
274                Self::ParseInt(e) => ColumnCountParseErrorOwned::ParseInt(e.to_string()),
275            }
276        }
277    }
278
279    impl ColumnCountParseErrorOwned {
280        pub fn to_shared<'a>(&'a self) -> ColumnCountParseError<'a> {
281            match self {
282                Self::InvalidValue(s) => ColumnCountParseError::InvalidValue(s),
283                Self::ParseInt(_) => ColumnCountParseError::InvalidValue("invalid integer"), /* Can't reconstruct */
284            }
285        }
286    }
287
288    pub fn parse_column_count<'a>(
289        input: &'a str,
290    ) -> Result<ColumnCount, ColumnCountParseError<'a>> {
291        let trimmed = input.trim();
292        if trimmed == "auto" {
293            return Ok(ColumnCount::Auto);
294        }
295        let val: u32 = trimmed
296            .parse()
297            .map_err(|e| ColumnCountParseError::ParseInt(e))?;
298        Ok(ColumnCount::Integer(val))
299    }
300
301    // -- ColumnWidth parser
302
303    #[derive(Clone, PartialEq)]
304    pub enum ColumnWidthParseError<'a> {
305        InvalidValue(&'a str),
306        PixelValue(CssPixelValueParseError<'a>),
307    }
308
309    impl_debug_as_display!(ColumnWidthParseError<'a>);
310    impl_display! { ColumnWidthParseError<'a>, {
311        InvalidValue(v) => format!("Invalid column-width value: \"{}\"", v),
312        PixelValue(e) => format!("{}", e),
313    }}
314    impl_from! { CssPixelValueParseError<'a>, ColumnWidthParseError::PixelValue }
315
316    #[derive(Debug, Clone, PartialEq)]
317    pub enum ColumnWidthParseErrorOwned {
318        InvalidValue(String),
319        PixelValue(CssPixelValueParseErrorOwned),
320    }
321
322    impl<'a> ColumnWidthParseError<'a> {
323        pub fn to_contained(&self) -> ColumnWidthParseErrorOwned {
324            match self {
325                Self::InvalidValue(s) => ColumnWidthParseErrorOwned::InvalidValue(s.to_string()),
326                Self::PixelValue(e) => ColumnWidthParseErrorOwned::PixelValue(e.to_contained()),
327            }
328        }
329    }
330
331    impl ColumnWidthParseErrorOwned {
332        pub fn to_shared<'a>(&'a self) -> ColumnWidthParseError<'a> {
333            match self {
334                Self::InvalidValue(s) => ColumnWidthParseError::InvalidValue(s),
335                Self::PixelValue(e) => ColumnWidthParseError::PixelValue(e.to_shared()),
336            }
337        }
338    }
339
340    pub fn parse_column_width<'a>(
341        input: &'a str,
342    ) -> Result<ColumnWidth, ColumnWidthParseError<'a>> {
343        let trimmed = input.trim();
344        if trimmed == "auto" {
345            return Ok(ColumnWidth::Auto);
346        }
347        Ok(ColumnWidth::Length(parse_pixel_value(trimmed)?))
348    }
349
350    // -- Other column parsers...
351    macro_rules! define_simple_column_parser {
352        (
353            $fn_name:ident,
354            $struct_name:ident,
355            $error_name:ident,
356            $error_owned_name:ident,
357            $prop_name:expr,
358            $($val:expr => $variant:path),+
359        ) => {
360            #[derive(Clone, PartialEq)]
361            pub enum $error_name<'a> {
362                InvalidValue(&'a str),
363            }
364
365            impl_debug_as_display!($error_name<'a>);
366            impl_display! { $error_name<'a>, {
367                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
368            }}
369
370            #[derive(Debug, Clone, PartialEq)]
371            pub enum $error_owned_name {
372                InvalidValue(String),
373            }
374
375            impl<'a> $error_name<'a> {
376                pub fn to_contained(&self) -> $error_owned_name {
377                    match self {
378                        Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string()),
379                    }
380                }
381            }
382
383            impl $error_owned_name {
384                pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
385                    match self {
386                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
387                    }
388                }
389            }
390
391            pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
392                match input.trim() {
393                    $( $val => Ok($variant), )+
394                    _ => Err($error_name::InvalidValue(input)),
395                }
396            }
397        };
398    }
399
400    define_simple_column_parser!(
401        parse_column_span,
402        ColumnSpan,
403        ColumnSpanParseError,
404        ColumnSpanParseErrorOwned,
405        "column-span",
406        "none" => ColumnSpan::None,
407        "all" => ColumnSpan::All
408    );
409
410    define_simple_column_parser!(
411        parse_column_fill,
412        ColumnFill,
413        ColumnFillParseError,
414        ColumnFillParseErrorOwned,
415        "column-fill",
416        "auto" => ColumnFill::Auto,
417        "balance" => ColumnFill::Balance
418    );
419
420    // Parsers for column-rule-*
421
422    #[derive(Clone, PartialEq)]
423    pub enum ColumnRuleWidthParseError<'a> {
424        Pixel(CssPixelValueParseError<'a>),
425    }
426    impl_debug_as_display!(ColumnRuleWidthParseError<'a>);
427    impl_display! { ColumnRuleWidthParseError<'a>, { Pixel(e) => format!("{}", e) }}
428    impl_from! { CssPixelValueParseError<'a>, ColumnRuleWidthParseError::Pixel }
429    #[derive(Debug, Clone, PartialEq)]
430    pub enum ColumnRuleWidthParseErrorOwned {
431        Pixel(CssPixelValueParseErrorOwned),
432    }
433    impl<'a> ColumnRuleWidthParseError<'a> {
434        pub fn to_contained(&self) -> ColumnRuleWidthParseErrorOwned {
435            match self {
436                ColumnRuleWidthParseError::Pixel(e) => {
437                    ColumnRuleWidthParseErrorOwned::Pixel(e.to_contained())
438                }
439            }
440        }
441    }
442    impl ColumnRuleWidthParseErrorOwned {
443        pub fn to_shared<'a>(&'a self) -> ColumnRuleWidthParseError<'a> {
444            match self {
445                ColumnRuleWidthParseErrorOwned::Pixel(e) => {
446                    ColumnRuleWidthParseError::Pixel(e.to_shared())
447                }
448            }
449        }
450    }
451    pub fn parse_column_rule_width<'a>(
452        input: &'a str,
453    ) -> Result<ColumnRuleWidth, ColumnRuleWidthParseError<'a>> {
454        Ok(ColumnRuleWidth {
455            inner: parse_pixel_value(input)?,
456        })
457    }
458
459    #[derive(Clone, PartialEq)]
460    pub enum ColumnRuleStyleParseError<'a> {
461        Style(CssBorderStyleParseError<'a>),
462    }
463    impl_debug_as_display!(ColumnRuleStyleParseError<'a>);
464    impl_display! { ColumnRuleStyleParseError<'a>, { Style(e) => format!("{}", e) }}
465    impl_from! { CssBorderStyleParseError<'a>, ColumnRuleStyleParseError::Style }
466    #[derive(Debug, Clone, PartialEq)]
467    pub enum ColumnRuleStyleParseErrorOwned {
468        Style(CssBorderStyleParseErrorOwned),
469    }
470    impl<'a> ColumnRuleStyleParseError<'a> {
471        pub fn to_contained(&self) -> ColumnRuleStyleParseErrorOwned {
472            match self {
473                ColumnRuleStyleParseError::Style(e) => {
474                    ColumnRuleStyleParseErrorOwned::Style(e.to_contained())
475                }
476            }
477        }
478    }
479    impl ColumnRuleStyleParseErrorOwned {
480        pub fn to_shared<'a>(&'a self) -> ColumnRuleStyleParseError<'a> {
481            match self {
482                ColumnRuleStyleParseErrorOwned::Style(e) => {
483                    ColumnRuleStyleParseError::Style(e.to_shared())
484                }
485            }
486        }
487    }
488    pub fn parse_column_rule_style<'a>(
489        input: &'a str,
490    ) -> Result<ColumnRuleStyle, ColumnRuleStyleParseError<'a>> {
491        Ok(ColumnRuleStyle {
492            inner: parse_border_style(input)?,
493        })
494    }
495
496    #[derive(Clone, PartialEq)]
497    pub enum ColumnRuleColorParseError<'a> {
498        Color(CssColorParseError<'a>),
499    }
500    impl_debug_as_display!(ColumnRuleColorParseError<'a>);
501    impl_display! { ColumnRuleColorParseError<'a>, { Color(e) => format!("{}", e) }}
502    impl_from! { CssColorParseError<'a>, ColumnRuleColorParseError::Color }
503    #[derive(Debug, Clone, PartialEq)]
504    pub enum ColumnRuleColorParseErrorOwned {
505        Color(CssColorParseErrorOwned),
506    }
507    impl<'a> ColumnRuleColorParseError<'a> {
508        pub fn to_contained(&self) -> ColumnRuleColorParseErrorOwned {
509            match self {
510                ColumnRuleColorParseError::Color(e) => {
511                    ColumnRuleColorParseErrorOwned::Color(e.to_contained())
512                }
513            }
514        }
515    }
516    impl ColumnRuleColorParseErrorOwned {
517        pub fn to_shared<'a>(&'a self) -> ColumnRuleColorParseError<'a> {
518            match self {
519                ColumnRuleColorParseErrorOwned::Color(e) => {
520                    ColumnRuleColorParseError::Color(e.to_shared())
521                }
522            }
523        }
524    }
525    pub fn parse_column_rule_color<'a>(
526        input: &'a str,
527    ) -> Result<ColumnRuleColor, ColumnRuleColorParseError<'a>> {
528        Ok(ColumnRuleColor {
529            inner: parse_css_color(input)?,
530        })
531    }
532}
533
534#[cfg(feature = "parser")]
535pub use parser::*;
536
537#[cfg(all(test, feature = "parser"))]
538mod tests {
539    use super::*;
540
541    #[test]
542    fn test_parse_column_count() {
543        assert_eq!(parse_column_count("auto").unwrap(), ColumnCount::Auto);
544        assert_eq!(parse_column_count("3").unwrap(), ColumnCount::Integer(3));
545        assert!(parse_column_count("none").is_err());
546        assert!(parse_column_count("2.5").is_err());
547    }
548
549    #[test]
550    fn test_parse_column_width() {
551        assert_eq!(parse_column_width("auto").unwrap(), ColumnWidth::Auto);
552        assert_eq!(
553            parse_column_width("200px").unwrap(),
554            ColumnWidth::Length(PixelValue::px(200.0))
555        );
556        assert_eq!(
557            parse_column_width("15em").unwrap(),
558            ColumnWidth::Length(PixelValue::em(15.0))
559        );
560        assert!(parse_column_width("50%").is_ok()); // Percentage is valid for column-width
561    }
562
563    #[test]
564    fn test_parse_column_span() {
565        assert_eq!(parse_column_span("none").unwrap(), ColumnSpan::None);
566        assert_eq!(parse_column_span("all").unwrap(), ColumnSpan::All);
567        assert!(parse_column_span("2").is_err());
568    }
569
570    #[test]
571    fn test_parse_column_fill() {
572        assert_eq!(parse_column_fill("auto").unwrap(), ColumnFill::Auto);
573        assert_eq!(parse_column_fill("balance").unwrap(), ColumnFill::Balance);
574        assert!(parse_column_fill("none").is_err());
575    }
576
577    #[test]
578    fn test_parse_column_rule() {
579        assert_eq!(
580            parse_column_rule_width("5px").unwrap().inner,
581            PixelValue::px(5.0)
582        );
583        assert_eq!(
584            parse_column_rule_style("dotted").unwrap().inner,
585            BorderStyle::Dotted
586        );
587        assert_eq!(parse_column_rule_color("blue").unwrap().inner, ColorU::BLUE);
588    }
589}