style/values/generics/
color.rs

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
7use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorFunction};
8use crate::derives::*;
9use crate::values::{
10    computed::ToComputedValue, specified::percentage::ToPercentage, ParseError, Parser,
11};
12use std::fmt::{self, Write};
13use 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)]
19pub 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)]
35pub struct ColorMixFlags(u8);
36bitflags! {
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)]
61pub 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
70pub use self::GenericColorMix as ColorMix;
71
72impl<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
119impl<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
140pub use self::GenericColor as Color;
141
142impl<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)]
187pub enum GenericColorOrAuto<C> {
188    /// A `<color>`.
189    Color(C),
190    /// `auto`
191    Auto,
192}
193
194pub 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)]
215pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
216
217impl<C> GenericCaretColor<C> {
218    /// Returns the `auto` value.
219    pub fn auto() -> Self {
220        GenericCaretColor(GenericColorOrAuto::Auto)
221    }
222}
223
224pub 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)]
232pub 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
239impl<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
261impl<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}