Skip to main content

hjkl_css/
value.rs

1//! Property values. v1 covers the handful needed by phase-1 properties
2//! (background-color, color, padding, margin, width, height); phase-2
3//! adds Auto, Number, FontFamilyList, Border, and SideSet for mixed
4//! length/auto shorthands.
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum Value {
8    Color(Color),
9    Length(Length),
10    /// 1..=4 lengths, as in CSS shorthand: `padding: 1px` /
11    /// `padding: 1px 2px` / `padding: 1px 2px 3px` /
12    /// `padding: 1px 2px 3px 4px`.
13    LengthSet(Vec<Length>),
14    Keyword(String),
15    /// `auto` keyword used with sizing and margin properties.
16    Auto,
17    /// Unitless number — `flex-grow`, `flex-shrink`, `line-height`,
18    /// `font-weight` (numeric form).
19    Number(f64),
20    /// `font-family` list: one or more family names in source order.
21    /// Quoted strings and bare idents both land here as plain strings.
22    FontFamilyList(Vec<String>),
23    /// `border` / `border-{side}` / `outline` shorthand.
24    /// `style` is dropped — floem has no border-style model; `border: 1px
25    /// none #fff` treats `none` as zero width (same as omitting a
26    /// visible line). The `solid` token, if present, is accepted and
27    /// ignored.
28    Border {
29        width: Length,
30        color: Color,
31    },
32    /// 1..=4 side values where each side may be a length or `auto`.
33    /// Used for `margin` / `padding` shorthands that contain `auto`.
34    ///
35    /// Trade-off: a separate `SideSet` variant rather than making `Length`
36    /// an `Option<Length>` or adding `Length::Auto`. Keeping `Length` as
37    /// a pure numeric type avoids propagating `auto`-awareness into every
38    /// length consumer; adapters that only care about `LengthSet` stay
39    /// unchanged.
40    SideSet(Vec<SideValue>),
41}
42
43/// One side in a mixed length/auto shorthand (`margin: 4px auto`).
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum SideValue {
46    Length(Length),
47    Auto,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Color {
52    pub r: u8,
53    pub g: u8,
54    pub b: u8,
55    pub a: u8,
56}
57
58/// `Length` carries `f64` payloads so it intentionally only implements
59/// `PartialEq` — `f64` is not `Eq`. By extension, [`Value::LengthSet`]
60/// and any container of [`Value`] cannot be `Eq` either. Adapters that
61/// want hashable/`Eq`-equivalent comparison should compare a derived
62/// representation (e.g. rendered floem `Style`).
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub enum Length {
65    /// CSS pixels. Unitless values in the source are parsed as Px too —
66    /// floem doesn't distinguish, and the layout engine treats both the
67    /// same.
68    ///
69    /// `em` / `rem` are deferred to a later phase; document the gap here
70    /// so it is easy to find.
71    Px(f64),
72    /// `<n>%` of the parent's relevant dimension.
73    Percent(f64),
74}
75
76impl Color {
77    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
78        Self { r, g, b, a }
79    }
80
81    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
82        Self::rgba(r, g, b, 0xff)
83    }
84}
85
86impl Length {
87    pub fn as_px(self) -> Option<f64> {
88        match self {
89            Self::Px(v) => Some(v),
90            Self::Percent(_) => None,
91        }
92    }
93}
94
95/// Expand a 1..=4 length shorthand to the four sides (top, right, bottom,
96/// left). Mirrors CSS's `padding` / `margin` rules.
97///
98/// Returns `None` for empty or 5+-length input. The parser only emits
99/// `LengthSet`s in the 1..=4 range, so adapters consuming `Value::LengthSet`
100/// values that came from `parse` can `.unwrap()` safely. Adapter code
101/// constructing a `LengthSet` programmatically should guard against the
102/// out-of-range case.
103pub fn expand_sides(set: &[Length]) -> Option<[Length; 4]> {
104    match set.len() {
105        1 => Some([set[0]; 4]),
106        2 => Some([set[0], set[1], set[0], set[1]]),
107        3 => Some([set[0], set[1], set[2], set[1]]),
108        4 => Some([set[0], set[1], set[2], set[3]]),
109        _ => None,
110    }
111}
112
113/// Expand a 1..=4 `SideValue` shorthand to four sides.
114pub fn expand_side_set(set: &[SideValue]) -> Option<[SideValue; 4]> {
115    match set.len() {
116        1 => Some([set[0]; 4]),
117        2 => Some([set[0], set[1], set[0], set[1]]),
118        3 => Some([set[0], set[1], set[2], set[1]]),
119        4 => Some([set[0], set[1], set[2], set[3]]),
120        _ => None,
121    }
122}