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        self.interpolation.to_css(dest)?;
95        dest.write_str(", ")?;
96        self.left.to_css(dest)?;
97        if !can_omit(&self.left_percentage, &self.right_percentage, true) {
98            dest.write_char(' ')?;
99            self.left_percentage.to_css(dest)?;
100        }
101        dest.write_str(", ")?;
102        self.right.to_css(dest)?;
103        if !can_omit(&self.right_percentage, &self.left_percentage, false) {
104            dest.write_char(' ')?;
105            self.right_percentage.to_css(dest)?;
106        }
107        dest.write_char(')')
108    }
109}
110
111impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> {
112    /// Mix the colors so that we get a single color. If any of the 2 colors are
113    /// not mixable (perhaps not absolute?), then return None.
114    pub fn mix_to_absolute(&self) -> Option<AbsoluteColor>
115    where
116        Percentage: ToPercentage,
117    {
118        let left = self.left.as_absolute()?;
119        let right = self.right.as_absolute()?;
120
121        Some(crate::color::mix::mix(
122            self.interpolation,
123            &left,
124            self.left_percentage.to_percentage(),
125            &right,
126            self.right_percentage.to_percentage(),
127            self.flags,
128        ))
129    }
130}
131
132pub use self::GenericColor as Color;
133
134impl<Percentage> Color<Percentage> {
135    /// If this color is absolute return it's value, otherwise return None.
136    pub fn as_absolute(&self) -> Option<&AbsoluteColor> {
137        match *self {
138            Self::Absolute(ref absolute) => Some(absolute),
139            _ => None,
140        }
141    }
142
143    /// Returns a color value representing currentcolor.
144    pub fn currentcolor() -> Self {
145        Self::CurrentColor
146    }
147
148    /// Whether it is a currentcolor value (no numeric color component).
149    pub fn is_currentcolor(&self) -> bool {
150        matches!(*self, Self::CurrentColor)
151    }
152
153    /// Whether this color is an absolute color.
154    pub fn is_absolute(&self) -> bool {
155        matches!(*self, Self::Absolute(..))
156    }
157}
158
159/// Either `<color>` or `auto`.
160#[derive(
161    Animate,
162    Clone,
163    ComputeSquaredDistance,
164    Copy,
165    Debug,
166    MallocSizeOf,
167    PartialEq,
168    Parse,
169    SpecifiedValueInfo,
170    ToAnimatedValue,
171    ToAnimatedZero,
172    ToComputedValue,
173    ToResolvedValue,
174    ToCss,
175    ToShmem,
176    ToTyped,
177)]
178#[repr(C, u8)]
179pub enum GenericColorOrAuto<C> {
180    /// A `<color>`.
181    Color(C),
182    /// `auto`
183    Auto,
184}
185
186pub use self::GenericColorOrAuto as ColorOrAuto;
187
188/// Caret color is effectively a ColorOrAuto, but resolves `auto` to
189/// currentColor.
190#[derive(
191    Animate,
192    Clone,
193    ComputeSquaredDistance,
194    Copy,
195    Debug,
196    MallocSizeOf,
197    PartialEq,
198    SpecifiedValueInfo,
199    ToAnimatedValue,
200    ToAnimatedZero,
201    ToComputedValue,
202    ToCss,
203    ToShmem,
204    ToTyped,
205)]
206#[repr(transparent)]
207pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
208
209impl<C> GenericCaretColor<C> {
210    /// Returns the `auto` value.
211    pub fn auto() -> Self {
212        GenericCaretColor(GenericColorOrAuto::Auto)
213    }
214}
215
216pub use self::GenericCaretColor as CaretColor;
217
218/// A light-dark(<light>, <dark>) function.
219#[derive(
220    Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, ToCss, ToResolvedValue,
221)]
222#[css(function = "light-dark", comma)]
223#[repr(C)]
224pub struct GenericLightDark<T> {
225    /// The value returned when using a light theme.
226    pub light: T,
227    /// The value returned when using a dark theme.
228    pub dark: T,
229}
230
231impl<T> GenericLightDark<T> {
232    /// Parse the arguments of the light-dark() function.
233    pub fn parse_args_with<'i>(
234        input: &mut Parser<'i, '_>,
235        mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
236    ) -> Result<Self, ParseError<'i>> {
237        let light = parse_one(input)?;
238        input.expect_comma()?;
239        let dark = parse_one(input)?;
240        Ok(Self { light, dark })
241    }
242
243    /// Parse the light-dark() function.
244    pub fn parse_with<'i>(
245        input: &mut Parser<'i, '_>,
246        parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
247    ) -> Result<Self, ParseError<'i>> {
248        input.expect_function_matching("light-dark")?;
249        input.parse_nested_block(|input| Self::parse_args_with(input, parse_one))
250    }
251}
252
253impl<T: ToComputedValue> GenericLightDark<T> {
254    /// Choose the light or dark version of this value for computation purposes, and compute it.
255    pub fn compute(&self, cx: &crate::values::computed::Context) -> T::ComputedValue {
256        let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme);
257        if cx.for_non_inherited_property {
258            cx.rule_cache_conditions
259                .borrow_mut()
260                .set_color_scheme_dependency(cx.builder.color_scheme);
261        }
262        let chosen = if dark { &self.dark } else { &self.light };
263        chosen.to_computed_value(cx)
264    }
265}