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}
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    ToTyped,
175)]
176#[repr(C, u8)]
177pub enum GenericColorOrAuto<C> {
178    /// A `<color>`.
179    Color(C),
180    /// `auto`
181    Auto,
182}
183
184pub use self::GenericColorOrAuto as ColorOrAuto;
185
186/// Caret color is effectively a ColorOrAuto, but resolves `auto` to
187/// currentColor.
188#[derive(
189    Animate,
190    Clone,
191    ComputeSquaredDistance,
192    Copy,
193    Debug,
194    MallocSizeOf,
195    PartialEq,
196    SpecifiedValueInfo,
197    ToAnimatedValue,
198    ToAnimatedZero,
199    ToComputedValue,
200    ToCss,
201    ToShmem,
202    ToTyped,
203)]
204#[repr(transparent)]
205pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
206
207impl<C> GenericCaretColor<C> {
208    /// Returns the `auto` value.
209    pub fn auto() -> Self {
210        GenericCaretColor(GenericColorOrAuto::Auto)
211    }
212}
213
214pub use self::GenericCaretColor as CaretColor;
215
216/// A light-dark(<light>, <dark>) function.
217#[derive(
218    Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, ToCss, ToResolvedValue,
219)]
220#[css(function = "light-dark", comma)]
221#[repr(C)]
222pub struct GenericLightDark<T> {
223    /// The value returned when using a light theme.
224    pub light: T,
225    /// The value returned when using a dark theme.
226    pub dark: T,
227}
228
229impl<T> GenericLightDark<T> {
230    /// Parse the arguments of the light-dark() function.
231    pub fn parse_args_with<'i>(
232        input: &mut Parser<'i, '_>,
233        mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
234    ) -> Result<Self, ParseError<'i>> {
235        let light = parse_one(input)?;
236        input.expect_comma()?;
237        let dark = parse_one(input)?;
238        Ok(Self { light, dark })
239    }
240
241    /// Parse the light-dark() function.
242    pub fn parse_with<'i>(
243        input: &mut Parser<'i, '_>,
244        parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
245    ) -> Result<Self, ParseError<'i>> {
246        input.expect_function_matching("light-dark")?;
247        input.parse_nested_block(|input| Self::parse_args_with(input, parse_one))
248    }
249}
250
251impl<T: ToComputedValue> GenericLightDark<T> {
252    /// Choose the light or dark version of this value for computation purposes, and compute it.
253    pub fn compute(&self, cx: &crate::values::computed::Context) -> T::ComputedValue {
254        let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme);
255        if cx.for_non_inherited_property {
256            cx.rule_cache_conditions
257                .borrow_mut()
258                .set_color_scheme_dependency(cx.builder.color_scheme);
259        }
260        let chosen = if dark { &self.dark } else { &self.light };
261        chosen.to_computed_value(cx)
262    }
263}