use crate::compat::Feature;
use crate::context::PropertyHandlerContext;
use crate::declaration::{DeclarationBlock, DeclarationList};
use crate::error::{ParserError, PrinterError};
use crate::macros::{define_shorthand, enum_property, shorthand_property};
use crate::printer::Printer;
use crate::properties::{Property, PropertyId};
use crate::targets::{Browsers, Targets};
use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
use crate::values::color::CssColor;
use crate::values::number::CSSNumber;
use crate::values::string::CowArcStr;
use crate::values::url::Url;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use bitflags::bitflags;
use cssparser::*;
use smallvec::SmallVec;
use super::custom::Token;
use super::{CustomProperty, CustomPropertyName, TokenList, TokenOrValue};
enum_property! {
pub enum Resize {
None,
Both,
Horizontal,
Vertical,
Block,
Inline,
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct CursorImage<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub url: Url<'i>,
pub hotspot: Option<(CSSNumber, CSSNumber)>,
}
impl<'i> Parse<'i> for CursorImage<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let url = Url::parse(input)?;
let hotspot = if let Ok(x) = input.try_parse(CSSNumber::parse) {
let y = CSSNumber::parse(input)?;
Some((x, y))
} else {
None
};
Ok(CursorImage { url, hotspot })
}
}
impl<'i> ToCss for CursorImage<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.url.to_css(dest)?;
if let Some((x, y)) = self.hotspot {
dest.write_char(' ')?;
x.to_css(dest)?;
dest.write_char(' ')?;
y.to_css(dest)?;
}
Ok(())
}
}
enum_property! {
#[allow(missing_docs)]
pub enum CursorKeyword {
"auto": Auto,
"default": Default,
"none": None,
"context-menu": ContextMenu,
"help": Help,
"pointer": Pointer,
"progress": Progress,
"wait": Wait,
"cell": Cell,
"crosshair": Crosshair,
"text": Text,
"vertical-text": VerticalText,
"alias": Alias,
"copy": Copy,
"move": Move,
"no-drop": NoDrop,
"not-allowed": NotAllowed,
"grab": Grab,
"grabbing": Grabbing,
"e-resize": EResize,
"n-resize": NResize,
"ne-resize": NeResize,
"nw-resize": NwResize,
"s-resize": SResize,
"se-resize": SeResize,
"sw-resize": SwResize,
"w-resize": WResize,
"ew-resize": EwResize,
"ns-resize": NsResize,
"nesw-resize": NeswResize,
"nwse-resize": NwseResize,
"col-resize": ColResize,
"row-resize": RowResize,
"all-scroll": AllScroll,
"zoom-in": ZoomIn,
"zoom-out": ZoomOut,
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Cursor<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub images: SmallVec<[CursorImage<'i>; 1]>,
pub keyword: CursorKeyword,
}
impl<'i> Parse<'i> for Cursor<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut images = SmallVec::new();
loop {
match input.try_parse(CursorImage::parse) {
Ok(image) => images.push(image),
Err(_) => break,
}
input.expect_comma()?;
}
Ok(Cursor {
images,
keyword: CursorKeyword::parse(input)?,
})
}
}
impl<'i> ToCss for Cursor<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
for image in &self.images {
image.to_css(dest)?;
dest.delim(',', false)?;
}
self.keyword.to_css(dest)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ColorOrAuto {
Auto,
Color(CssColor),
}
impl Default for ColorOrAuto {
fn default() -> ColorOrAuto {
ColorOrAuto::Auto
}
}
impl<'i> Parse<'i> for ColorOrAuto {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
return Ok(ColorOrAuto::Auto);
}
let color = CssColor::parse(input)?;
Ok(ColorOrAuto::Color(color))
}
}
impl ToCss for ColorOrAuto {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
ColorOrAuto::Auto => dest.write_str("auto"),
ColorOrAuto::Color(color) => color.to_css(dest),
}
}
}
impl FallbackValues for ColorOrAuto {
fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
match self {
ColorOrAuto::Color(color) => color
.get_fallbacks(targets)
.into_iter()
.map(|color| ColorOrAuto::Color(color))
.collect(),
ColorOrAuto::Auto => Vec::new(),
}
}
}
impl IsCompatible for ColorOrAuto {
fn is_compatible(&self, browsers: Browsers) -> bool {
match self {
ColorOrAuto::Color(color) => color.is_compatible(browsers),
ColorOrAuto::Auto => true,
}
}
}
enum_property! {
pub enum CaretShape {
Auto,
Bar,
Block,
Underscore,
}
}
impl Default for CaretShape {
fn default() -> CaretShape {
CaretShape::Auto
}
}
shorthand_property! {
pub struct Caret {
color: CaretColor(ColorOrAuto),
shape: CaretShape(CaretShape),
}
}
impl FallbackValues for Caret {
fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
self
.color
.get_fallbacks(targets)
.into_iter()
.map(|color| Caret {
color,
shape: self.shape.clone(),
})
.collect()
}
}
impl IsCompatible for Caret {
fn is_compatible(&self, browsers: Browsers) -> bool {
self.color.is_compatible(browsers)
}
}
enum_property! {
pub enum UserSelect {
Auto,
Text,
None,
Contain,
All,
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[allow(missing_docs)]
pub enum Appearance<'i> {
None,
Auto,
Textfield,
MenulistButton,
Button,
Checkbox,
Listbox,
Menulist,
Meter,
ProgressBar,
PushButton,
Radio,
Searchfield,
SliderHorizontal,
SquareButton,
Textarea,
NonStandard(CowArcStr<'i>),
}
impl<'i> Appearance<'i> {
fn from_str(name: &str) -> Option<Self> {
Some(match_ignore_ascii_case! { &name,
"none" => Appearance::None,
"auto" => Appearance::Auto,
"textfield" => Appearance::Textfield,
"menulist-button" => Appearance::MenulistButton,
"button" => Appearance::Button,
"checkbox" => Appearance::Checkbox,
"listbox" => Appearance::Listbox,
"menulist" => Appearance::Menulist,
"meter" => Appearance::Meter,
"progress-bar" => Appearance::ProgressBar,
"push-button" => Appearance::PushButton,
"radio" => Appearance::Radio,
"searchfield" => Appearance::Searchfield,
"slider-horizontal" => Appearance::SliderHorizontal,
"square-button" => Appearance::SquareButton,
"textarea" => Appearance::Textarea,
_ => return None
})
}
fn to_str(&self) -> &str {
match self {
Appearance::None => "none",
Appearance::Auto => "auto",
Appearance::Textfield => "textfield",
Appearance::MenulistButton => "menulist-button",
Appearance::Button => "button",
Appearance::Checkbox => "checkbox",
Appearance::Listbox => "listbox",
Appearance::Menulist => "menulist",
Appearance::Meter => "meter",
Appearance::ProgressBar => "progress-bar",
Appearance::PushButton => "push-button",
Appearance::Radio => "radio",
Appearance::Searchfield => "searchfield",
Appearance::SliderHorizontal => "slider-horizontal",
Appearance::SquareButton => "square-button",
Appearance::Textarea => "textarea",
Appearance::NonStandard(s) => s.as_ref(),
}
}
}
impl<'i> Parse<'i> for Appearance<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let ident = input.expect_ident()?;
Ok(Self::from_str(ident.as_ref()).unwrap_or_else(|| Appearance::NonStandard(ident.into())))
}
}
impl<'i> ToCss for Appearance<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
dest.write_str(self.to_str())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'i> serde::Serialize for Appearance<'i> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_str())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'i, 'de: 'i> serde::Deserialize<'de> for Appearance<'i> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = CowArcStr::deserialize(deserializer)?;
Ok(Self::from_str(s.as_ref()).unwrap_or_else(|| Appearance::NonStandard(s)))
}
}
#[cfg(feature = "jsonschema")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
impl<'a> schemars::JsonSchema for Appearance<'a> {
fn is_referenceable() -> bool {
true
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
str::json_schema(gen)
}
fn schema_name() -> String {
"Appearance".into()
}
}
bitflags! {
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedColorScheme", into = "SerializedColorScheme"))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ColorScheme: u8 {
const Light = 0b01;
const Dark = 0b10;
const Only = 0b100;
}
}
impl<'i> Parse<'i> for ColorScheme {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut res = ColorScheme::empty();
let ident = input.expect_ident()?;
match_ignore_ascii_case! { &ident,
"normal" => return Ok(res),
"only" => res |= ColorScheme::Only,
"light" => res |= ColorScheme::Light,
"dark" => res |= ColorScheme::Dark,
_ => {}
};
while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
match_ignore_ascii_case! { &ident,
"normal" => return Err(input.new_custom_error(ParserError::InvalidValue)),
"only" => {
if res.contains(ColorScheme::Only) {
return Err(input.new_custom_error(ParserError::InvalidValue));
}
res |= ColorScheme::Only;
return Ok(res);
},
"light" => res |= ColorScheme::Light,
"dark" => res |= ColorScheme::Dark,
_ => {}
};
}
Ok(res)
}
}
impl ToCss for ColorScheme {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if self.is_empty() {
return dest.write_str("normal");
}
if self.contains(ColorScheme::Light) {
dest.write_str("light")?;
if self.contains(ColorScheme::Dark) {
dest.write_char(' ')?;
}
}
if self.contains(ColorScheme::Dark) {
dest.write_str("dark")?;
}
if self.contains(ColorScheme::Only) {
dest.write_str(" only")?;
}
Ok(())
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
struct SerializedColorScheme {
light: bool,
dark: bool,
only: bool,
}
impl From<ColorScheme> for SerializedColorScheme {
fn from(color_scheme: ColorScheme) -> Self {
Self {
light: color_scheme.contains(ColorScheme::Light),
dark: color_scheme.contains(ColorScheme::Dark),
only: color_scheme.contains(ColorScheme::Only),
}
}
}
impl From<SerializedColorScheme> for ColorScheme {
fn from(s: SerializedColorScheme) -> ColorScheme {
let mut color_scheme = ColorScheme::empty();
color_scheme.set(ColorScheme::Light, s.light);
color_scheme.set(ColorScheme::Dark, s.dark);
color_scheme.set(ColorScheme::Only, s.only);
color_scheme
}
}
#[cfg(feature = "jsonschema")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
impl<'a> schemars::JsonSchema for ColorScheme {
fn is_referenceable() -> bool {
true
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
SerializedColorScheme::json_schema(gen)
}
fn schema_name() -> String {
"ColorScheme".into()
}
}
#[derive(Default)]
pub(crate) struct ColorSchemeHandler;
impl<'i> PropertyHandler<'i> for ColorSchemeHandler {
fn handle_property(
&mut self,
property: &Property<'i>,
dest: &mut DeclarationList<'i>,
context: &mut PropertyHandlerContext<'i, '_>,
) -> bool {
match property {
Property::ColorScheme(color_scheme) => {
if !context.targets.is_compatible(Feature::LightDark) {
if color_scheme.contains(ColorScheme::Light) {
dest.push(define_var("--lightningcss-light", Token::Ident("initial".into())));
dest.push(define_var("--lightningcss-dark", Token::WhiteSpace(" ".into())));
if color_scheme.contains(ColorScheme::Dark) {
context.add_dark_rule(define_var("--lightningcss-light", Token::WhiteSpace(" ".into())));
context.add_dark_rule(define_var("--lightningcss-dark", Token::Ident("initial".into())));
}
} else if color_scheme.contains(ColorScheme::Dark) {
dest.push(define_var("--lightningcss-light", Token::WhiteSpace(" ".into())));
dest.push(define_var("--lightningcss-dark", Token::Ident("initial".into())));
}
}
dest.push(property.clone());
true
}
_ => false,
}
}
fn finalize(&mut self, _: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {}
}
#[inline]
fn define_var<'i>(name: &'static str, value: Token<'static>) -> Property<'i> {
Property::Custom(CustomProperty {
name: CustomPropertyName::Custom(name.into()),
value: TokenList(vec![TokenOrValue::Token(value)]),
})
}