whisker_css/shorthand/
background.rs1use core::fmt;
4
5use crate::css::Css;
6use crate::data_type::Color;
7use crate::data_type_ext::Position;
8use crate::keyword::{
9 BackgroundAttachment, BackgroundClip, BackgroundOrigin, BackgroundRepeat, BackgroundSize,
10};
11use crate::to_css::ToCss;
12use crate::value::ImageRef;
13
14#[derive(Clone, Debug, PartialEq)]
17pub struct BackgroundLayer {
18 pub image: ImageRef,
20 pub position: Option<Position>,
22 pub size: Option<BackgroundSize>,
24 pub repeat: Option<BackgroundRepeat>,
26 pub attachment: Option<BackgroundAttachment>,
28 pub origin: Option<BackgroundOrigin>,
30 pub clip: Option<BackgroundClip>,
32}
33
34impl BackgroundLayer {
35 pub fn new(image: impl Into<ImageRef>) -> Self {
37 Self {
38 image: image.into(),
39 position: None,
40 size: None,
41 repeat: None,
42 attachment: None,
43 origin: None,
44 clip: None,
45 }
46 }
47
48 pub fn position(mut self, p: Position) -> Self {
50 self.position = Some(p);
51 self
52 }
53
54 pub fn size(mut self, sz: BackgroundSize) -> Self {
56 self.size = Some(sz);
57 self
58 }
59
60 pub fn repeat(mut self, r: BackgroundRepeat) -> Self {
62 self.repeat = Some(r);
63 self
64 }
65
66 pub fn attachment(mut self, a: BackgroundAttachment) -> Self {
68 self.attachment = Some(a);
69 self
70 }
71
72 pub fn origin(mut self, o: BackgroundOrigin) -> Self {
74 self.origin = Some(o);
75 self
76 }
77
78 pub fn clip(mut self, c: BackgroundClip) -> Self {
80 self.clip = Some(c);
81 self
82 }
83}
84
85impl ToCss for BackgroundLayer {
86 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
87 self.image.to_css(dest)?;
88 if let Some(p) = &self.position {
89 dest.write_char(' ')?;
90 p.to_css(dest)?;
91 if let Some(sz) = &self.size {
92 dest.write_str(" / ")?;
93 sz.to_css(dest)?;
94 }
95 } else if let Some(sz) = &self.size {
96 dest.write_str(" 0 0 / ")?;
100 sz.to_css(dest)?;
101 }
102 if let Some(r) = &self.repeat {
103 dest.write_char(' ')?;
104 r.to_css(dest)?;
105 }
106 if let Some(a) = &self.attachment {
107 dest.write_char(' ')?;
108 a.to_css(dest)?;
109 }
110 if let Some(o) = &self.origin {
111 dest.write_char(' ')?;
112 o.to_css(dest)?;
113 }
114 if let Some(c) = &self.clip {
115 dest.write_char(' ')?;
116 c.to_css(dest)?;
117 }
118 Ok(())
119 }
120}
121
122#[derive(Clone, Debug, Default, PartialEq)]
129pub struct Background {
130 pub layers: Vec<BackgroundLayer>,
132 pub color: Option<Color>,
134}
135
136impl Background {
137 pub fn new() -> Self {
139 Self::default()
140 }
141
142 pub fn layer(mut self, l: BackgroundLayer) -> Self {
144 self.layers.push(l);
145 self
146 }
147
148 pub fn color(mut self, c: Color) -> Self {
150 self.color = Some(c);
151 self
152 }
153}
154
155impl ToCss for Background {
156 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
157 let mut wrote = false;
158 for layer in &self.layers {
159 if wrote {
160 dest.write_str(", ")?;
161 }
162 layer.to_css(dest)?;
163 wrote = true;
164 }
165 if let Some(c) = &self.color {
166 if wrote {
167 dest.write_char(' ')?;
168 }
169 c.to_css(dest)?;
170 }
171 Ok(())
172 }
173}
174
175impl Css {
176 pub fn background(self, b: Background) -> Self {
179 self.push("background", b)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use crate::data_type::{Color, ColorStop, CssString, Gradient, NamedColor};
186 use crate::data_type_ext::{Position, PositionKeyword};
187 use crate::keyword::*;
188 use crate::value::ImageRef;
189 use crate::Css;
190
191 use super::*;
192
193 #[test]
194 fn background_color_only() {
195 let s = Css::new().background(Background::new().color(Color::Named(NamedColor::Red)));
196 assert_eq!(s.to_string(), "background: red;");
197 }
198
199 #[test]
200 fn background_image_url_with_repeat() {
201 let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
202 .repeat(BackgroundRepeat::NoRepeat);
203 let s = Css::new().background(Background::new().layer(layer));
204 assert_eq!(s.to_string(), "background: url(\"a.png\") no-repeat;");
205 }
206
207 #[test]
208 fn background_gradient_with_color_trailing() {
209 let layer = BackgroundLayer::new(Gradient::linear_to_bottom([
210 ColorStop::new(NamedColor::Red.into()),
211 ColorStop::new(NamedColor::Blue.into()),
212 ]));
213 let s = Css::new().background(
214 Background::new()
215 .layer(layer)
216 .color(Color::Named(NamedColor::White)),
217 );
218 assert_eq!(
219 s.to_string(),
220 "background: linear-gradient(to bottom, red, blue) white;"
221 );
222 }
223
224 #[test]
225 fn background_multiple_layers() {
226 let l1 = BackgroundLayer::new(ImageRef::Url(CssString::new("top.png")))
227 .repeat(BackgroundRepeat::NoRepeat);
228 let l2 = BackgroundLayer::new(ImageRef::Url(CssString::new("base.png")));
229 let s = Css::new().background(Background::new().layer(l1).layer(l2));
230 assert_eq!(
231 s.to_string(),
232 "background: url(\"top.png\") no-repeat, url(\"base.png\");"
233 );
234 }
235
236 #[test]
237 fn background_layer_size_with_position() {
238 let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
239 .position(Position::Keyword(PositionKeyword::Center))
240 .size(BackgroundSize::Cover);
241 let s = Css::new().background(Background::new().layer(layer));
242 assert_eq!(s.to_string(), "background: url(\"a.png\") center / cover;");
243 }
244
245 #[test]
246 fn background_layer_size_without_position_inserts_zero() {
247 let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
248 .size(BackgroundSize::Cover);
249 let s = Css::new().background(Background::new().layer(layer));
250 assert_eq!(s.to_string(), "background: url(\"a.png\") 0 0 / cover;");
251 }
252
253 #[test]
254 fn background_layer_origin_clip_attachment() {
255 let layer = BackgroundLayer::new(ImageRef::None)
256 .attachment(BackgroundAttachment::Fixed)
257 .origin(BackgroundOrigin::ContentBox)
258 .clip(BackgroundClip::PaddingBox);
259 let s = Css::new().background(Background::new().layer(layer));
260 assert_eq!(
261 s.to_string(),
262 "background: none fixed content-box padding-box;"
263 );
264 }
265}