use core::fmt;
use crate::css::Css;
use crate::data_type::Color;
use crate::data_type_ext::Position;
use crate::keyword::{
BackgroundAttachment, BackgroundClip, BackgroundOrigin, BackgroundRepeat, BackgroundSize,
};
use crate::to_css::ToCss;
use crate::value::ImageRef;
#[derive(Clone, Debug, PartialEq)]
pub struct BackgroundLayer {
pub image: ImageRef,
pub position: Option<Position>,
pub size: Option<BackgroundSize>,
pub repeat: Option<BackgroundRepeat>,
pub attachment: Option<BackgroundAttachment>,
pub origin: Option<BackgroundOrigin>,
pub clip: Option<BackgroundClip>,
}
impl BackgroundLayer {
pub fn new(image: impl Into<ImageRef>) -> Self {
Self {
image: image.into(),
position: None,
size: None,
repeat: None,
attachment: None,
origin: None,
clip: None,
}
}
pub fn position(mut self, p: Position) -> Self {
self.position = Some(p);
self
}
pub fn size(mut self, sz: BackgroundSize) -> Self {
self.size = Some(sz);
self
}
pub fn repeat(mut self, r: BackgroundRepeat) -> Self {
self.repeat = Some(r);
self
}
pub fn attachment(mut self, a: BackgroundAttachment) -> Self {
self.attachment = Some(a);
self
}
pub fn origin(mut self, o: BackgroundOrigin) -> Self {
self.origin = Some(o);
self
}
pub fn clip(mut self, c: BackgroundClip) -> Self {
self.clip = Some(c);
self
}
}
impl ToCss for BackgroundLayer {
fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
self.image.to_css(dest)?;
if let Some(p) = &self.position {
dest.write_char(' ')?;
p.to_css(dest)?;
if let Some(sz) = &self.size {
dest.write_str(" / ")?;
sz.to_css(dest)?;
}
} else if let Some(sz) = &self.size {
dest.write_str(" 0 0 / ")?;
sz.to_css(dest)?;
}
if let Some(r) = &self.repeat {
dest.write_char(' ')?;
r.to_css(dest)?;
}
if let Some(a) = &self.attachment {
dest.write_char(' ')?;
a.to_css(dest)?;
}
if let Some(o) = &self.origin {
dest.write_char(' ')?;
o.to_css(dest)?;
}
if let Some(c) = &self.clip {
dest.write_char(' ')?;
c.to_css(dest)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Background {
pub layers: Vec<BackgroundLayer>,
pub color: Option<Color>,
}
impl Background {
pub fn new() -> Self {
Self::default()
}
pub fn layer(mut self, l: BackgroundLayer) -> Self {
self.layers.push(l);
self
}
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
}
impl ToCss for Background {
fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
let mut wrote = false;
for layer in &self.layers {
if wrote {
dest.write_str(", ")?;
}
layer.to_css(dest)?;
wrote = true;
}
if let Some(c) = &self.color {
if wrote {
dest.write_char(' ')?;
}
c.to_css(dest)?;
}
Ok(())
}
}
impl Css {
pub fn background(self, b: Background) -> Self {
self.push("background", b)
}
}
#[cfg(test)]
mod tests {
use crate::data_type::{Color, ColorStop, CssString, Gradient, NamedColor};
use crate::data_type_ext::{Position, PositionKeyword};
use crate::keyword::*;
use crate::value::ImageRef;
use crate::Css;
use super::*;
#[test]
fn background_color_only() {
let s = Css::new().background(Background::new().color(Color::Named(NamedColor::Red)));
assert_eq!(s.to_string(), "background: red;");
}
#[test]
fn background_image_url_with_repeat() {
let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
.repeat(BackgroundRepeat::NoRepeat);
let s = Css::new().background(Background::new().layer(layer));
assert_eq!(s.to_string(), "background: url(\"a.png\") no-repeat;");
}
#[test]
fn background_gradient_with_color_trailing() {
let layer = BackgroundLayer::new(Gradient::linear_to_bottom([
ColorStop::new(NamedColor::Red.into()),
ColorStop::new(NamedColor::Blue.into()),
]));
let s = Css::new().background(
Background::new()
.layer(layer)
.color(Color::Named(NamedColor::White)),
);
assert_eq!(
s.to_string(),
"background: linear-gradient(to bottom, red, blue) white;"
);
}
#[test]
fn background_multiple_layers() {
let l1 = BackgroundLayer::new(ImageRef::Url(CssString::new("top.png")))
.repeat(BackgroundRepeat::NoRepeat);
let l2 = BackgroundLayer::new(ImageRef::Url(CssString::new("base.png")));
let s = Css::new().background(Background::new().layer(l1).layer(l2));
assert_eq!(
s.to_string(),
"background: url(\"top.png\") no-repeat, url(\"base.png\");"
);
}
#[test]
fn background_layer_size_with_position() {
let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
.position(Position::Keyword(PositionKeyword::Center))
.size(BackgroundSize::Cover);
let s = Css::new().background(Background::new().layer(layer));
assert_eq!(s.to_string(), "background: url(\"a.png\") center / cover;");
}
#[test]
fn background_layer_size_without_position_inserts_zero() {
let layer = BackgroundLayer::new(ImageRef::Url(CssString::new("a.png")))
.size(BackgroundSize::Cover);
let s = Css::new().background(Background::new().layer(layer));
assert_eq!(s.to_string(), "background: url(\"a.png\") 0 0 / cover;");
}
#[test]
fn background_layer_origin_clip_attachment() {
let layer = BackgroundLayer::new(ImageRef::None)
.attachment(BackgroundAttachment::Fixed)
.origin(BackgroundOrigin::ContentBox)
.clip(BackgroundClip::PaddingBox);
let s = Css::new().background(Background::new().layer(layer));
assert_eq!(
s.to_string(),
"background: none fixed content-box padding-box;"
);
}
}