Skip to main content

whisker_css/shorthand/
border.rs

1//! `border` shorthand builder.
2
3use crate::css::Css;
4use crate::data_type::{Color, LengthPercentage};
5use crate::keyword::BorderStyle;
6
7/// Builder for the `border` family. Any subset of width/style/color
8/// can be set; only the populated dimensions are pushed to the
9/// style. Use [`Css::border`] to apply to all four sides;
10/// [`Css::border_top`], [`Css::border_right`],
11/// [`Css::border_bottom`], [`Css::border_left`] target one side.
12#[derive(Clone, Debug, Default, PartialEq)]
13pub struct Border {
14    /// Width to apply.
15    pub width: Option<LengthPercentage>,
16    /// Line style to apply.
17    pub style: Option<BorderStyle>,
18    /// Color to apply.
19    pub color: Option<Color>,
20}
21
22impl Border {
23    /// An empty builder.
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Set the border width.
29    pub fn width(mut self, v: impl Into<LengthPercentage>) -> Self {
30        self.width = Some(v.into());
31        self
32    }
33
34    /// Set the border style.
35    pub fn style(mut self, v: BorderStyle) -> Self {
36        self.style = Some(v);
37        self
38    }
39
40    /// Set the border style to [`BorderStyle::Solid`].
41    pub fn solid(self) -> Self {
42        self.style(BorderStyle::Solid)
43    }
44
45    /// Set the border style to [`BorderStyle::Dashed`].
46    pub fn dashed(self) -> Self {
47        self.style(BorderStyle::Dashed)
48    }
49
50    /// Set the border style to [`BorderStyle::Dotted`].
51    pub fn dotted(self) -> Self {
52        self.style(BorderStyle::Dotted)
53    }
54
55    /// Set the border color.
56    pub fn color(mut self, v: Color) -> Self {
57        self.color = Some(v);
58        self
59    }
60}
61
62impl Css {
63    /// Sets `border` for all four sides. Equivalent to setting
64    /// `border-top`, `border-right`, `border-bottom`, `border-left`
65    /// individually.
66    /// <https://lynxjs.org/api/css/properties/border>
67    pub fn border(self, b: Border) -> Self {
68        self.border_top(b.clone())
69            .border_right(b.clone())
70            .border_bottom(b.clone())
71            .border_left(b)
72    }
73
74    /// Sets `border-top` only.
75    /// <https://lynxjs.org/api/css/properties/border-top>
76    pub fn border_top(mut self, b: Border) -> Self {
77        if let Some(w) = b.width {
78            self = self.border_top_width(w);
79        }
80        if let Some(s) = b.style {
81            self = self.border_top_style(s);
82        }
83        if let Some(c) = b.color {
84            self = self.border_top_color(c);
85        }
86        self
87    }
88
89    /// Sets `border-right` only.
90    /// <https://lynxjs.org/api/css/properties/border-right>
91    pub fn border_right(mut self, b: Border) -> Self {
92        if let Some(w) = b.width {
93            self = self.border_right_width(w);
94        }
95        if let Some(s) = b.style {
96            self = self.border_right_style(s);
97        }
98        if let Some(c) = b.color {
99            self = self.border_right_color(c);
100        }
101        self
102    }
103
104    /// Sets `border-bottom` only.
105    /// <https://lynxjs.org/api/css/properties/border-bottom>
106    pub fn border_bottom(mut self, b: Border) -> Self {
107        if let Some(w) = b.width {
108            self = self.border_bottom_width(w);
109        }
110        if let Some(s) = b.style {
111            self = self.border_bottom_style(s);
112        }
113        if let Some(c) = b.color {
114            self = self.border_bottom_color(c);
115        }
116        self
117    }
118
119    /// Sets `border-left` only.
120    /// <https://lynxjs.org/api/css/properties/border-left>
121    pub fn border_left(mut self, b: Border) -> Self {
122        if let Some(w) = b.width {
123            self = self.border_left_width(w);
124        }
125        if let Some(s) = b.style {
126            self = self.border_left_style(s);
127        }
128        if let Some(c) = b.color {
129            self = self.border_left_color(c);
130        }
131        self
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::data_type::Color;
138    use crate::ext::*;
139    use crate::keyword::BorderStyle;
140    use crate::Css;
141
142    use super::*;
143
144    #[test]
145    fn border_full() {
146        let s = Css::new().border(
147            Border::new()
148                .width(px(1))
149                .solid()
150                .color(Color::hex(0xCCCCCC)),
151        );
152        assert_eq!(
153            s.to_string(),
154            "border-top-width: 1px; border-top-style: solid; border-top-color: rgb(204, 204, 204); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(204, 204, 204); border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 204, 204); border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204);"
155        );
156    }
157
158    #[test]
159    fn border_partial_only_style() {
160        let s = Css::new().border(Border::new().solid());
161        assert_eq!(
162            s.to_string(),
163            "border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid;"
164        );
165    }
166
167    #[test]
168    fn border_bottom_overrides_border() {
169        let s = Css::new()
170            .border(
171                Border::new()
172                    .width(px(1))
173                    .solid()
174                    .color(Color::hex(0x000000)),
175            )
176            .border_bottom(Border::new().width(px(3)).color(Color::hex(0xFF0000)));
177        // Last-write-wins keeps the first 3 sides at width 1px and
178        // overrides bottom to 3px / red.
179        let css = s.to_string();
180        assert!(css.contains("border-bottom-width: 3px"));
181        assert!(css.contains("border-bottom-color: rgb(255, 0, 0)"));
182        assert!(css.contains("border-bottom-style: solid"));
183        assert!(css.contains("border-top-width: 1px"));
184    }
185
186    #[test]
187    fn border_style_constructors() {
188        let solid = Border::new().solid();
189        let dashed = Border::new().dashed();
190        let dotted = Border::new().dotted();
191        assert_eq!(solid.style, Some(BorderStyle::Solid));
192        assert_eq!(dashed.style, Some(BorderStyle::Dashed));
193        assert_eq!(dotted.style, Some(BorderStyle::Dotted));
194    }
195
196    #[test]
197    fn border_per_side_individual() {
198        let s = Css::new()
199            .border_top(Border::new().width(px(1)).solid())
200            .border_right(Border::new().width(px(2)).dashed())
201            .border_bottom(Border::new().width(px(3)).dotted())
202            .border_left(Border::new().width(px(4)).style(BorderStyle::Double));
203        assert_eq!(
204            s.to_string(),
205            "border-top-width: 1px; border-top-style: solid; border-right-width: 2px; border-right-style: dashed; border-bottom-width: 3px; border-bottom-style: dotted; border-left-width: 4px; border-left-style: double;"
206        );
207    }
208}