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