color.rs (8065B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Generic types for color properties. 6 7 use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorFunction}; 8 use crate::derives::*; 9 use crate::values::{ 10 computed::ToComputedValue, specified::percentage::ToPercentage, ParseError, Parser, 11 }; 12 use std::fmt::{self, Write}; 13 use style_traits::{CssWriter, ToCss}; 14 15 /// This struct represents a combined color from a numeric color and 16 /// the current foreground color (currentcolor keyword). 17 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem, ToTyped)] 18 #[repr(C)] 19 pub enum GenericColor<Percentage> { 20 /// The actual numeric color. 21 Absolute(AbsoluteColor), 22 /// A unresolvable color. 23 ColorFunction(Box<ColorFunction<Self>>), 24 /// The `CurrentColor` keyword. 25 CurrentColor, 26 /// The color-mix() function. 27 ColorMix(Box<GenericColorMix<Self, Percentage>>), 28 /// The contrast-color() function. 29 ContrastColor(Box<Self>), 30 } 31 32 /// Flags used to modify the calculation of a color mix result. 33 #[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)] 34 #[repr(C)] 35 pub struct ColorMixFlags(u8); 36 bitflags! { 37 impl ColorMixFlags : u8 { 38 /// Normalize the weights of the mix. 39 const NORMALIZE_WEIGHTS = 1 << 0; 40 /// The result should always be converted to the modern color syntax. 41 const RESULT_IN_MODERN_SYNTAX = 1 << 1; 42 } 43 } 44 45 /// A restricted version of the css `color-mix()` function, which only supports 46 /// percentages. 47 /// 48 /// https://drafts.csswg.org/css-color-5/#color-mix 49 #[derive( 50 Clone, 51 Debug, 52 MallocSizeOf, 53 PartialEq, 54 ToAnimatedValue, 55 ToComputedValue, 56 ToResolvedValue, 57 ToShmem, 58 )] 59 #[allow(missing_docs)] 60 #[repr(C)] 61 pub struct GenericColorMix<Color, Percentage> { 62 pub interpolation: ColorInterpolationMethod, 63 pub left: Color, 64 pub left_percentage: Percentage, 65 pub right: Color, 66 pub right_percentage: Percentage, 67 pub flags: ColorMixFlags, 68 } 69 70 pub use self::GenericColorMix as ColorMix; 71 72 impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> { 73 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 74 where 75 W: Write, 76 { 77 fn can_omit<Percentage: ToPercentage>( 78 percent: &Percentage, 79 other: &Percentage, 80 is_left: bool, 81 ) -> bool { 82 if percent.is_calc() { 83 return false; 84 } 85 if percent.to_percentage() == 0.5 { 86 return other.to_percentage() == 0.5; 87 } 88 if is_left { 89 return false; 90 } 91 (1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON 92 } 93 94 dest.write_str("color-mix(")?; 95 96 // If the color interpolation method is oklab (which is now the default), 97 // it can be omitted. 98 // See: https://github.com/web-platform-tests/interop/issues/1166 99 if !self.interpolation.is_default() { 100 self.interpolation.to_css(dest)?; 101 dest.write_str(", ")?; 102 } 103 104 self.left.to_css(dest)?; 105 if !can_omit(&self.left_percentage, &self.right_percentage, true) { 106 dest.write_char(' ')?; 107 self.left_percentage.to_css(dest)?; 108 } 109 dest.write_str(", ")?; 110 self.right.to_css(dest)?; 111 if !can_omit(&self.right_percentage, &self.left_percentage, false) { 112 dest.write_char(' ')?; 113 self.right_percentage.to_css(dest)?; 114 } 115 dest.write_char(')') 116 } 117 } 118 119 impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> { 120 /// Mix the colors so that we get a single color. If any of the 2 colors are 121 /// not mixable (perhaps not absolute?), then return None. 122 pub fn mix_to_absolute(&self) -> Option<AbsoluteColor> 123 where 124 Percentage: ToPercentage, 125 { 126 let left = self.left.as_absolute()?; 127 let right = self.right.as_absolute()?; 128 129 Some(crate::color::mix::mix( 130 self.interpolation, 131 &left, 132 self.left_percentage.to_percentage(), 133 &right, 134 self.right_percentage.to_percentage(), 135 self.flags, 136 )) 137 } 138 } 139 140 pub use self::GenericColor as Color; 141 142 impl<Percentage> Color<Percentage> { 143 /// If this color is absolute return it's value, otherwise return None. 144 pub fn as_absolute(&self) -> Option<&AbsoluteColor> { 145 match *self { 146 Self::Absolute(ref absolute) => Some(absolute), 147 _ => None, 148 } 149 } 150 151 /// Returns a color value representing currentcolor. 152 pub fn currentcolor() -> Self { 153 Self::CurrentColor 154 } 155 156 /// Whether it is a currentcolor value (no numeric color component). 157 pub fn is_currentcolor(&self) -> bool { 158 matches!(*self, Self::CurrentColor) 159 } 160 161 /// Whether this color is an absolute color. 162 pub fn is_absolute(&self) -> bool { 163 matches!(*self, Self::Absolute(..)) 164 } 165 } 166 167 /// Either `<color>` or `auto`. 168 #[derive( 169 Animate, 170 Clone, 171 ComputeSquaredDistance, 172 Copy, 173 Debug, 174 MallocSizeOf, 175 PartialEq, 176 Parse, 177 SpecifiedValueInfo, 178 ToAnimatedValue, 179 ToAnimatedZero, 180 ToComputedValue, 181 ToResolvedValue, 182 ToCss, 183 ToShmem, 184 ToTyped, 185 )] 186 #[repr(C, u8)] 187 pub enum GenericColorOrAuto<C> { 188 /// A `<color>`. 189 Color(C), 190 /// `auto` 191 Auto, 192 } 193 194 pub use self::GenericColorOrAuto as ColorOrAuto; 195 196 /// Caret color is effectively a ColorOrAuto, but resolves `auto` to 197 /// currentColor. 198 #[derive( 199 Animate, 200 Clone, 201 ComputeSquaredDistance, 202 Copy, 203 Debug, 204 MallocSizeOf, 205 PartialEq, 206 SpecifiedValueInfo, 207 ToAnimatedValue, 208 ToAnimatedZero, 209 ToComputedValue, 210 ToCss, 211 ToShmem, 212 ToTyped, 213 )] 214 #[repr(transparent)] 215 pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>); 216 217 impl<C> GenericCaretColor<C> { 218 /// Returns the `auto` value. 219 pub fn auto() -> Self { 220 GenericCaretColor(GenericColorOrAuto::Auto) 221 } 222 } 223 224 pub use self::GenericCaretColor as CaretColor; 225 226 /// A light-dark(<light>, <dark>) function. 227 #[derive( 228 Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, ToCss, ToResolvedValue, 229 )] 230 #[css(function = "light-dark", comma)] 231 #[repr(C)] 232 pub struct GenericLightDark<T> { 233 /// The value returned when using a light theme. 234 pub light: T, 235 /// The value returned when using a dark theme. 236 pub dark: T, 237 } 238 239 impl<T> GenericLightDark<T> { 240 /// Parse the arguments of the light-dark() function. 241 pub fn parse_args_with<'i>( 242 input: &mut Parser<'i, '_>, 243 mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>, 244 ) -> Result<Self, ParseError<'i>> { 245 let light = parse_one(input)?; 246 input.expect_comma()?; 247 let dark = parse_one(input)?; 248 Ok(Self { light, dark }) 249 } 250 251 /// Parse the light-dark() function. 252 pub fn parse_with<'i>( 253 input: &mut Parser<'i, '_>, 254 parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>, 255 ) -> Result<Self, ParseError<'i>> { 256 input.expect_function_matching("light-dark")?; 257 input.parse_nested_block(|input| Self::parse_args_with(input, parse_one)) 258 } 259 } 260 261 impl<T: ToComputedValue> GenericLightDark<T> { 262 /// Choose the light or dark version of this value for computation purposes, and compute it. 263 pub fn compute(&self, cx: &crate::values::computed::Context) -> T::ComputedValue { 264 let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme); 265 if cx.for_non_inherited_property { 266 cx.rule_cache_conditions 267 .borrow_mut() 268 .set_color_scheme_dependency(cx.builder.color_scheme); 269 } 270 let chosen = if dark { &self.dark } else { &self.light }; 271 chosen.to_computed_value(cx) 272 } 273 }