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}