//! CSS properties related to user interface. 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::{should_compile, 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! { /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property. pub enum Resize { /// The element does not allow resizing. None, /// The element is resizable in both the x and y directions. Both, /// The element is resizable in the x direction. Horizontal, /// The element is resizable in the y direction. Vertical, /// The element is resizable in the block direction, according to the writing mode. Block, /// The element is resizable in the inline direction, according to the writing mode. Inline, } } /// A [cursor image](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value, used in the `cursor` property. /// /// See [Cursor](Cursor). #[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> { /// A url to the cursor image. #[cfg_attr(feature = "serde", serde(borrow))] pub url: Url<'i>, /// The location in the image where the mouse pointer appears. pub hotspot: Option<(CSSNumber, CSSNumber)>, } impl<'i> Parse<'i> for CursorImage<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { 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(&self, dest: &mut Printer) -> 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! { /// A pre-defined [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) value, /// used in the `cursor` property. /// /// See [Cursor](Cursor). #[allow(missing_docs)] pub enum CursorKeyword { Auto, Default, None, ContextMenu, Help, Pointer, Progress, Wait, Cell, Crosshair, Text, VerticalText, Alias, Copy, Move, NoDrop, NotAllowed, Grab, Grabbing, EResize, NResize, NeResize, NwResize, SResize, SeResize, SwResize, WResize, EwResize, NsResize, NeswResize, NwseResize, ColResize, RowResize, AllScroll, ZoomIn, ZoomOut, } } /// A value for the [cursor](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#cursor) property. #[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> { /// A list of cursor images. #[cfg_attr(feature = "serde", serde(borrow))] pub images: SmallVec<[CursorImage<'i>; 1]>, /// A pre-defined cursor. pub keyword: CursorKeyword, } impl<'i> Parse<'i> for Cursor<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { 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(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { for image in &self.images { image.to_css(dest)?; dest.delim(',', false)?; } self.keyword.to_css(dest) } } /// A value for the [caret-color](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-color) property. #[derive(Debug, Clone, PartialEq, Parse, ToCss)] #[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 { /// The `currentColor`, adjusted by the UA to ensure contrast against the background. Auto, /// A color. Color(CssColor), } impl Default for ColorOrAuto { fn default() -> ColorOrAuto { ColorOrAuto::Auto } } impl FallbackValues for ColorOrAuto { fn get_fallbacks(&mut self, targets: Targets) -> Vec { 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! { /// A value for the [caret-shape](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret-shape) property. pub enum CaretShape { /// The UA determines the caret shape. Auto, /// A thin bar caret. Bar, /// A rectangle caret. Block, /// An underscore caret. Underscore, } } impl Default for CaretShape { fn default() -> CaretShape { CaretShape::Auto } } shorthand_property! { /// A value for the [caret](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#caret) shorthand property. pub struct Caret { /// The caret color. color: CaretColor(ColorOrAuto), /// The caret shape. shape: CaretShape(CaretShape), } } impl FallbackValues for Caret { fn get_fallbacks(&mut self, targets: Targets) -> Vec { 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! { /// A value for the [user-select](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#content-selection) property. pub enum UserSelect { /// The UA determines whether text is selectable. Auto, /// Text is selectable. Text, /// Text is not selectable. None, /// Text selection is contained to the element. Contain, /// Only the entire element is selectable. All, } } /// A value for the [appearance](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#appearance-switching) property. #[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 { 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>> { 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(&self, dest: &mut Printer) -> 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(&self, serializer: S) -> Result 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(deserializer: D) -> Result 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! { /// A value for the [color-scheme](https://drafts.csswg.org/css-color-adjust/#color-scheme-prop) property. #[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 { /// Indicates that the element supports a light color scheme. const Light = 0b01; /// Indicates that the element supports a dark color scheme. const Dark = 0b10; /// Forbids the user agent from overriding the color scheme for the element. const Only = 0b100; } } impl<'i> Parse<'i> for ColorScheme { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { 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" => { // Only must be at the start or the end, not in the middle. 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(&self, dest: &mut Printer) -> 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 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 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 should_compile!(context.targets, 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)]), }) }