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