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