azul-css 0.0.7

Common datatypes used for styling applications using the Azul desktop GUI framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
//! CSS properties for table layout and styling.
//!
//! This module contains properties specific to CSS table formatting:
//! - `table-layout`: Controls the algorithm used to layout table cells, rows, and columns
//! - `border-collapse`: Specifies whether cell borders are collapsed into a single border or
//!   separated
//! - `border-spacing`: Sets the distance between borders of adjacent cells (separate borders only)
//! - `caption-side`: Specifies the placement of a table caption
//! - `empty-cells`: Specifies whether or not to display borders on empty cells in a table

use alloc::string::{String, ToString};

use crate::{
    format_rust_code::FormatAsRustCode,
    props::{
        basic::pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
        formatter::PrintAsCssValue,
        macros::PixelValueTaker,
    },
};

// table-layout

/// Controls the algorithm used to lay out table cells, rows, and columns.
///
/// The `table-layout` property determines whether the browser should use:
/// - **auto**: Column widths are determined by the content (slower but flexible)
/// - **fixed**: Column widths are determined by the first row (faster and predictable)
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum LayoutTableLayout {
    /// Use automatic table layout algorithm (content-based, default).
    /// Column width is set by the widest unbreakable content in the cells.
    Auto,
    /// Use fixed table layout algorithm (first-row-based).
    /// Column width is set by the width property of the column or first-row cell.
    /// Renders faster than auto.
    Fixed,
}

impl Default for LayoutTableLayout {
    fn default() -> Self {
        Self::Auto
    }
}

impl PrintAsCssValue for LayoutTableLayout {
    fn print_as_css_value(&self) -> String {
        match self {
            LayoutTableLayout::Auto => "auto".to_string(),
            LayoutTableLayout::Fixed => "fixed".to_string(),
        }
    }
}

impl FormatAsRustCode for LayoutTableLayout {
    fn format_as_rust_code(&self, _tabs: usize) -> String {
        match self {
            LayoutTableLayout::Auto => "LayoutTableLayout::Auto".to_string(),
            LayoutTableLayout::Fixed => "LayoutTableLayout::Fixed".to_string(),
        }
    }
}

// border-collapse

/// Specifies whether cell borders are collapsed into a single border or separated.
///
/// The `border-collapse` property determines the border rendering model:
/// - **separate**: Each cell has its own border (default, uses border-spacing)
/// - **collapse**: Adjacent cells share borders (ignores border-spacing)
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum StyleBorderCollapse {
    /// Borders are separated (default). Each cell has its own border.
    /// The `border-spacing` property defines the distance between borders.
    Separate,
    /// Borders are collapsed. Adjacent cells share a single border.
    /// Border conflict resolution rules apply when borders differ.
    Collapse,
}

impl Default for StyleBorderCollapse {
    fn default() -> Self {
        Self::Separate
    }
}

impl PrintAsCssValue for StyleBorderCollapse {
    fn print_as_css_value(&self) -> String {
        match self {
            StyleBorderCollapse::Separate => "separate".to_string(),
            StyleBorderCollapse::Collapse => "collapse".to_string(),
        }
    }
}

impl FormatAsRustCode for StyleBorderCollapse {
    fn format_as_rust_code(&self, _tabs: usize) -> String {
        match self {
            StyleBorderCollapse::Separate => "StyleBorderCollapse::Separate".to_string(),
            StyleBorderCollapse::Collapse => "StyleBorderCollapse::Collapse".to_string(),
        }
    }
}

// border-spacing

/// Sets the distance between the borders of adjacent cells.
///
/// The `border-spacing` property is only applicable when `border-collapse` is set to `separate`.
/// It can have one or two values:
/// - One value: Sets both horizontal and vertical spacing
/// - Two values: First is horizontal, second is vertical
///
/// This struct represents a single spacing value (either horizontal or vertical).
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct LayoutBorderSpacing {
    /// Horizontal spacing between cell borders
    pub horizontal: PixelValue,
    /// Vertical spacing between cell borders
    pub vertical: PixelValue,
}

impl Default for LayoutBorderSpacing {
    fn default() -> Self {
        // Default border-spacing is 0 (no spacing)
        Self {
            horizontal: PixelValue::const_px(0),
            vertical: PixelValue::const_px(0),
        }
    }
}

impl LayoutBorderSpacing {
    /// Creates a new border spacing with the same value for horizontal and vertical
    pub const fn new(spacing: PixelValue) -> Self {
        Self {
            horizontal: spacing,
            vertical: spacing,
        }
    }

    /// Creates a new border spacing with different horizontal and vertical values
    pub const fn new_separate(horizontal: PixelValue, vertical: PixelValue) -> Self {
        Self {
            horizontal,
            vertical,
        }
    }

    /// Creates a border spacing with zero spacing
    pub const fn zero() -> Self {
        Self {
            horizontal: PixelValue::const_px(0),
            vertical: PixelValue::const_px(0),
        }
    }
}

impl PrintAsCssValue for LayoutBorderSpacing {
    fn print_as_css_value(&self) -> String {
        if self.horizontal == self.vertical {
            // Single value: same for both dimensions
            self.horizontal.to_string()
        } else {
            // Two values: horizontal vertical
            format!("{} {}", self.horizontal, self.vertical)
        }
    }
}

impl FormatAsRustCode for LayoutBorderSpacing {
    fn format_as_rust_code(&self, _tabs: usize) -> String {
        use crate::format_rust_code::format_pixel_value;
        format!(
            "LayoutBorderSpacing {{ horizontal: {}, vertical: {} }}",
            format_pixel_value(&self.horizontal),
            format_pixel_value(&self.vertical)
        )
    }
}

// caption-side

/// Specifies the placement of a table caption.
///
/// The `caption-side` property positions the caption either above or below the table.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum StyleCaptionSide {
    /// Caption is placed above the table (default)
    Top,
    /// Caption is placed below the table
    Bottom,
}

impl Default for StyleCaptionSide {
    fn default() -> Self {
        Self::Top
    }
}

impl PrintAsCssValue for StyleCaptionSide {
    fn print_as_css_value(&self) -> String {
        match self {
            StyleCaptionSide::Top => "top".to_string(),
            StyleCaptionSide::Bottom => "bottom".to_string(),
        }
    }
}

impl FormatAsRustCode for StyleCaptionSide {
    fn format_as_rust_code(&self, _tabs: usize) -> String {
        match self {
            StyleCaptionSide::Top => "StyleCaptionSide::Top".to_string(),
            StyleCaptionSide::Bottom => "StyleCaptionSide::Bottom".to_string(),
        }
    }
}

// empty-cells

/// Specifies whether or not to display borders and background on empty cells.
///
/// The `empty-cells` property only applies when `border-collapse` is set to `separate`.
/// A cell is considered empty if it contains no visible content.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum StyleEmptyCells {
    /// Show borders and background on empty cells (default)
    Show,
    /// Hide borders and background on empty cells
    Hide,
}

impl Default for StyleEmptyCells {
    fn default() -> Self {
        Self::Show
    }
}

impl PrintAsCssValue for StyleEmptyCells {
    fn print_as_css_value(&self) -> String {
        match self {
            StyleEmptyCells::Show => "show".to_string(),
            StyleEmptyCells::Hide => "hide".to_string(),
        }
    }
}

impl FormatAsRustCode for StyleEmptyCells {
    fn format_as_rust_code(&self, _tabs: usize) -> String {
        match self {
            StyleEmptyCells::Show => "StyleEmptyCells::Show".to_string(),
            StyleEmptyCells::Hide => "StyleEmptyCells::Hide".to_string(),
        }
    }
}

// Parsing Functions

/// Parse errors for table-layout property
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutTableLayoutParseError<'a> {
    InvalidKeyword(&'a str),
}

/// Parse a table-layout value from a string
pub fn parse_table_layout<'a>(
    input: &'a str,
) -> Result<LayoutTableLayout, LayoutTableLayoutParseError<'a>> {
    match input.trim() {
        "auto" => Ok(LayoutTableLayout::Auto),
        "fixed" => Ok(LayoutTableLayout::Fixed),
        other => Err(LayoutTableLayoutParseError::InvalidKeyword(other)),
    }
}

/// Parse errors for border-collapse property
#[derive(Debug, Clone, PartialEq)]
pub enum StyleBorderCollapseParseError<'a> {
    InvalidKeyword(&'a str),
}

/// Parse a border-collapse value from a string
pub fn parse_border_collapse<'a>(
    input: &'a str,
) -> Result<StyleBorderCollapse, StyleBorderCollapseParseError<'a>> {
    match input.trim() {
        "separate" => Ok(StyleBorderCollapse::Separate),
        "collapse" => Ok(StyleBorderCollapse::Collapse),
        other => Err(StyleBorderCollapseParseError::InvalidKeyword(other)),
    }
}

/// Parse errors for border-spacing property
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutBorderSpacingParseError<'a> {
    PixelValue(CssPixelValueParseError<'a>),
    InvalidFormat,
}

/// Parse a border-spacing value from a string
/// Accepts: "5px" or "5px 10px"
pub fn parse_border_spacing<'a>(
    input: &'a str,
) -> Result<LayoutBorderSpacing, LayoutBorderSpacingParseError<'a>> {
    use crate::props::basic::parse_pixel_value;

    let parts: Vec<&str> = input.trim().split_whitespace().collect();

    match parts.len() {
        1 => {
            // Single value: use for both horizontal and vertical
            let value =
                parse_pixel_value(parts[0]).map_err(LayoutBorderSpacingParseError::PixelValue)?;
            Ok(LayoutBorderSpacing::new(value))
        }
        2 => {
            // Two values: horizontal vertical
            let horizontal =
                parse_pixel_value(parts[0]).map_err(LayoutBorderSpacingParseError::PixelValue)?;
            let vertical =
                parse_pixel_value(parts[1]).map_err(LayoutBorderSpacingParseError::PixelValue)?;
            Ok(LayoutBorderSpacing::new_separate(horizontal, vertical))
        }
        _ => Err(LayoutBorderSpacingParseError::InvalidFormat),
    }
}

/// Parse errors for caption-side property
#[derive(Debug, Clone, PartialEq)]
pub enum StyleCaptionSideParseError<'a> {
    InvalidKeyword(&'a str),
}

/// Parse a caption-side value from a string
pub fn parse_caption_side<'a>(
    input: &'a str,
) -> Result<StyleCaptionSide, StyleCaptionSideParseError<'a>> {
    match input.trim() {
        "top" => Ok(StyleCaptionSide::Top),
        "bottom" => Ok(StyleCaptionSide::Bottom),
        other => Err(StyleCaptionSideParseError::InvalidKeyword(other)),
    }
}

/// Parse errors for empty-cells property
#[derive(Debug, Clone, PartialEq)]
pub enum StyleEmptyCellsParseError<'a> {
    InvalidKeyword(&'a str),
}

/// Parse an empty-cells value from a string
pub fn parse_empty_cells<'a>(
    input: &'a str,
) -> Result<StyleEmptyCells, StyleEmptyCellsParseError<'a>> {
    match input.trim() {
        "show" => Ok(StyleEmptyCells::Show),
        "hide" => Ok(StyleEmptyCells::Hide),
        other => Err(StyleEmptyCellsParseError::InvalidKeyword(other)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_table_layout() {
        assert_eq!(parse_table_layout("auto").unwrap(), LayoutTableLayout::Auto);
        assert_eq!(
            parse_table_layout("fixed").unwrap(),
            LayoutTableLayout::Fixed
        );
        assert!(parse_table_layout("invalid").is_err());
    }

    #[test]
    fn test_parse_border_collapse() {
        assert_eq!(
            parse_border_collapse("separate").unwrap(),
            StyleBorderCollapse::Separate
        );
        assert_eq!(
            parse_border_collapse("collapse").unwrap(),
            StyleBorderCollapse::Collapse
        );
        assert!(parse_border_collapse("invalid").is_err());
    }

    #[test]
    fn test_parse_border_spacing() {
        let spacing1 = parse_border_spacing("5px").unwrap();
        assert_eq!(spacing1.horizontal, PixelValue::const_px(5));
        assert_eq!(spacing1.vertical, PixelValue::const_px(5));

        let spacing2 = parse_border_spacing("5px 10px").unwrap();
        assert_eq!(spacing2.horizontal, PixelValue::const_px(5));
        assert_eq!(spacing2.vertical, PixelValue::const_px(10));
    }

    #[test]
    fn test_parse_caption_side() {
        assert_eq!(parse_caption_side("top").unwrap(), StyleCaptionSide::Top);
        assert_eq!(
            parse_caption_side("bottom").unwrap(),
            StyleCaptionSide::Bottom
        );
        assert!(parse_caption_side("invalid").is_err());
    }

    #[test]
    fn test_parse_empty_cells() {
        assert_eq!(parse_empty_cells("show").unwrap(), StyleEmptyCells::Show);
        assert_eq!(parse_empty_cells("hide").unwrap(), StyleEmptyCells::Hide);
        assert!(parse_empty_cells("invalid").is_err());
    }
}