color.rs

  1use anyhow::{Context as _, bail};
  2use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
  3use serde::{
  4    Deserialize, Deserializer, Serialize, Serializer,
  5    de::{self, Visitor},
  6};
  7use std::{
  8    fmt::{self, Display, Formatter},
  9    hash::{Hash, Hasher},
 10};
 11
 12/// Convert an RGB hex color code number to a color type
 13pub fn rgb(hex: u32) -> Rgba {
 14    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
 15    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
 16    let b = (hex & 0xFF) as f32 / 255.0;
 17    Rgba { r, g, b, a: 1.0 }
 18}
 19
 20/// Convert an RGBA hex color code number to [`Rgba`]
 21pub fn rgba(hex: u32) -> Rgba {
 22    let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
 23    let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
 24    let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
 25    let a = (hex & 0xFF) as f32 / 255.0;
 26    Rgba { r, g, b, a }
 27}
 28
 29/// Swap from RGBA with premultiplied alpha to BGRA
 30pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
 31    color.swap(0, 2);
 32    if color[3] > 0 {
 33        let a = color[3] as f32 / 255.;
 34        color[0] = (color[0] as f32 / a) as u8;
 35        color[1] = (color[1] as f32 / a) as u8;
 36        color[2] = (color[2] as f32 / a) as u8;
 37    }
 38}
 39
 40/// An RGBA color
 41#[derive(PartialEq, Clone, Copy, Default)]
 42pub struct Rgba {
 43    /// The red component of the color, in the range 0.0 to 1.0
 44    pub r: f32,
 45    /// The green component of the color, in the range 0.0 to 1.0
 46    pub g: f32,
 47    /// The blue component of the color, in the range 0.0 to 1.0
 48    pub b: f32,
 49    /// The alpha component of the color, in the range 0.0 to 1.0
 50    pub a: f32,
 51}
 52
 53impl fmt::Debug for Rgba {
 54    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 55        write!(f, "rgba({:#010x})", u32::from(*self))
 56    }
 57}
 58
 59impl Rgba {
 60    /// Create a new [`Rgba`] color by blending this and another color together
 61    pub fn blend(&self, other: Rgba) -> Self {
 62        if other.a >= 1.0 {
 63            other
 64        } else if other.a <= 0.0 {
 65            return *self;
 66        } else {
 67            return Rgba {
 68                r: (self.r * (1.0 - other.a)) + (other.r * other.a),
 69                g: (self.g * (1.0 - other.a)) + (other.g * other.a),
 70                b: (self.b * (1.0 - other.a)) + (other.b * other.a),
 71                a: self.a,
 72            };
 73        }
 74    }
 75}
 76
 77impl From<Rgba> for u32 {
 78    fn from(rgba: Rgba) -> Self {
 79        let r = (rgba.r * 255.0) as u32;
 80        let g = (rgba.g * 255.0) as u32;
 81        let b = (rgba.b * 255.0) as u32;
 82        let a = (rgba.a * 255.0) as u32;
 83        (r << 24) | (g << 16) | (b << 8) | a
 84    }
 85}
 86
 87struct RgbaVisitor;
 88
 89impl Visitor<'_> for RgbaVisitor {
 90    type Value = Rgba;
 91
 92    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
 93        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
 94    }
 95
 96    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
 97        Rgba::try_from(value).map_err(E::custom)
 98    }
 99}
100
101impl JsonSchema for Rgba {
102    fn schema_name() -> String {
103        "Rgba".to_string()
104    }
105
106    fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
107        use schemars::schema::{InstanceType, SchemaObject, StringValidation};
108
109        Schema::Object(SchemaObject {
110            instance_type: Some(InstanceType::String.into()),
111            string: Some(Box::new(StringValidation {
112                pattern: Some(
113                    r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$".to_string(),
114                ),
115                ..Default::default()
116            })),
117            ..Default::default()
118        })
119    }
120}
121
122impl<'de> Deserialize<'de> for Rgba {
123    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
124        deserializer.deserialize_str(RgbaVisitor)
125    }
126}
127
128impl Serialize for Rgba {
129    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: Serializer,
132    {
133        let r = (self.r * 255.0).round() as u8;
134        let g = (self.g * 255.0).round() as u8;
135        let b = (self.b * 255.0).round() as u8;
136        let a = (self.a * 255.0).round() as u8;
137
138        let s = format!("#{r:02x}{g:02x}{b:02x}{a:02x}");
139        serializer.serialize_str(&s)
140    }
141}
142
143impl From<Hsla> for Rgba {
144    fn from(color: Hsla) -> Self {
145        let h = color.h;
146        let s = color.s;
147        let l = color.l;
148
149        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
150        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
151        let m = l - c / 2.0;
152        let cm = c + m;
153        let xm = x + m;
154
155        let (r, g, b) = match (h * 6.0).floor() as i32 {
156            0 | 6 => (cm, xm, m),
157            1 => (xm, cm, m),
158            2 => (m, cm, xm),
159            3 => (m, xm, cm),
160            4 => (xm, m, cm),
161            _ => (cm, m, xm),
162        };
163
164        Rgba {
165            r,
166            g,
167            b,
168            a: color.a,
169        }
170    }
171}
172
173impl TryFrom<&'_ str> for Rgba {
174    type Error = anyhow::Error;
175
176    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
177        const RGB: usize = "rgb".len();
178        const RGBA: usize = "rgba".len();
179        const RRGGBB: usize = "rrggbb".len();
180        const RRGGBBAA: usize = "rrggbbaa".len();
181
182        const EXPECTED_FORMATS: &str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa";
183        const INVALID_UNICODE: &str = "invalid unicode characters in color";
184
185        let Some(("", hex)) = value.trim().split_once('#') else {
186            bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}");
187        };
188
189        let (r, g, b, a) = match hex.len() {
190            RGB | RGBA => {
191                let r = u8::from_str_radix(
192                    hex.get(0..1).with_context(|| {
193                        format!("{INVALID_UNICODE}: r component of #rgb/#rgba for value: '{value}'")
194                    })?,
195                    16,
196                )?;
197                let g = u8::from_str_radix(
198                    hex.get(1..2).with_context(|| {
199                        format!("{INVALID_UNICODE}: g component of #rgb/#rgba for value: '{value}'")
200                    })?,
201                    16,
202                )?;
203                let b = u8::from_str_radix(
204                    hex.get(2..3).with_context(|| {
205                        format!("{INVALID_UNICODE}: b component of #rgb/#rgba for value: '{value}'")
206                    })?,
207                    16,
208                )?;
209                let a = if hex.len() == RGBA {
210                    u8::from_str_radix(
211                        hex.get(3..4).with_context(|| {
212                            format!("{INVALID_UNICODE}: a component of #rgba for value: '{value}'")
213                        })?,
214                        16,
215                    )?
216                } else {
217                    0xf
218                };
219
220                /// Duplicates a given hex digit.
221                /// E.g., `0xf` -> `0xff`.
222                const fn duplicate(value: u8) -> u8 {
223                    (value << 4) | value
224                }
225
226                (duplicate(r), duplicate(g), duplicate(b), duplicate(a))
227            }
228            RRGGBB | RRGGBBAA => {
229                let r = u8::from_str_radix(
230                    hex.get(0..2).with_context(|| {
231                        format!(
232                            "{}: r component of #rrggbb/#rrggbbaa for value: '{}'",
233                            INVALID_UNICODE, value
234                        )
235                    })?,
236                    16,
237                )?;
238                let g = u8::from_str_radix(
239                    hex.get(2..4).with_context(|| {
240                        format!(
241                            "{INVALID_UNICODE}: g component of #rrggbb/#rrggbbaa for value: '{value}'"
242                        )
243                    })?,
244                    16,
245                )?;
246                let b = u8::from_str_radix(
247                    hex.get(4..6).with_context(|| {
248                        format!(
249                            "{INVALID_UNICODE}: b component of #rrggbb/#rrggbbaa for value: '{value}'"
250                        )
251                    })?,
252                    16,
253                )?;
254                let a = if hex.len() == RRGGBBAA {
255                    u8::from_str_radix(
256                        hex.get(6..8).with_context(|| {
257                            format!(
258                                "{INVALID_UNICODE}: a component of #rrggbbaa for value: '{value}'"
259                            )
260                        })?,
261                        16,
262                    )?
263                } else {
264                    0xff
265                };
266                (r, g, b, a)
267            }
268            _ => bail!("invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}"),
269        };
270
271        Ok(Rgba {
272            r: r as f32 / 255.,
273            g: g as f32 / 255.,
274            b: b as f32 / 255.,
275            a: a as f32 / 255.,
276        })
277    }
278}
279
280/// An HSLA color
281#[derive(Default, Copy, Clone, Debug)]
282#[repr(C)]
283pub struct Hsla {
284    /// Hue, in a range from 0 to 1
285    pub h: f32,
286
287    /// Saturation, in a range from 0 to 1
288    pub s: f32,
289
290    /// Lightness, in a range from 0 to 1
291    pub l: f32,
292
293    /// Alpha, in a range from 0 to 1
294    pub a: f32,
295}
296
297impl PartialEq for Hsla {
298    fn eq(&self, other: &Self) -> bool {
299        self.h
300            .total_cmp(&other.h)
301            .then(self.s.total_cmp(&other.s))
302            .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
303            .is_eq()
304    }
305}
306
307impl PartialOrd for Hsla {
308    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
309        Some(self.cmp(other))
310    }
311}
312
313impl Ord for Hsla {
314    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
315        self.h
316            .total_cmp(&other.h)
317            .then(self.s.total_cmp(&other.s))
318            .then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
319    }
320}
321
322impl Eq for Hsla {}
323
324impl Hash for Hsla {
325    fn hash<H: Hasher>(&self, state: &mut H) {
326        state.write_u32(u32::from_be_bytes(self.h.to_be_bytes()));
327        state.write_u32(u32::from_be_bytes(self.s.to_be_bytes()));
328        state.write_u32(u32::from_be_bytes(self.l.to_be_bytes()));
329        state.write_u32(u32::from_be_bytes(self.a.to_be_bytes()));
330    }
331}
332
333impl Display for Hsla {
334    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
335        write!(
336            f,
337            "hsla({:.2}, {:.2}%, {:.2}%, {:.2})",
338            self.h * 360.,
339            self.s * 100.,
340            self.l * 100.,
341            self.a
342        )
343    }
344}
345
346/// Construct an [`Hsla`] object from plain values
347pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
348    Hsla {
349        h: h.clamp(0., 1.),
350        s: s.clamp(0., 1.),
351        l: l.clamp(0., 1.),
352        a: a.clamp(0., 1.),
353    }
354}
355
356/// Pure black in [`Hsla`]
357pub const fn black() -> Hsla {
358    Hsla {
359        h: 0.,
360        s: 0.,
361        l: 0.,
362        a: 1.,
363    }
364}
365
366/// Transparent black in [`Hsla`]
367pub const fn transparent_black() -> Hsla {
368    Hsla {
369        h: 0.,
370        s: 0.,
371        l: 0.,
372        a: 0.,
373    }
374}
375
376/// Transparent black in [`Hsla`]
377pub const fn transparent_white() -> Hsla {
378    Hsla {
379        h: 0.,
380        s: 0.,
381        l: 1.,
382        a: 0.,
383    }
384}
385
386/// Opaque grey in [`Hsla`], values will be clamped to the range [0, 1]
387pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
388    Hsla {
389        h: 0.,
390        s: 0.,
391        l: lightness.clamp(0., 1.),
392        a: opacity.clamp(0., 1.),
393    }
394}
395
396/// Pure white in [`Hsla`]
397pub const fn white() -> Hsla {
398    Hsla {
399        h: 0.,
400        s: 0.,
401        l: 1.,
402        a: 1.,
403    }
404}
405
406/// The color red in [`Hsla`]
407pub const fn red() -> Hsla {
408    Hsla {
409        h: 0.,
410        s: 1.,
411        l: 0.5,
412        a: 1.,
413    }
414}
415
416/// The color blue in [`Hsla`]
417pub const fn blue() -> Hsla {
418    Hsla {
419        h: 0.6666666667,
420        s: 1.,
421        l: 0.5,
422        a: 1.,
423    }
424}
425
426/// The color green in [`Hsla`]
427pub const fn green() -> Hsla {
428    Hsla {
429        h: 0.3333333333,
430        s: 1.,
431        l: 0.25,
432        a: 1.,
433    }
434}
435
436/// The color yellow in [`Hsla`]
437pub const fn yellow() -> Hsla {
438    Hsla {
439        h: 0.1666666667,
440        s: 1.,
441        l: 0.5,
442        a: 1.,
443    }
444}
445
446impl Hsla {
447    /// Converts this HSLA color to an RGBA color.
448    pub fn to_rgb(self) -> Rgba {
449        self.into()
450    }
451
452    /// The color red
453    pub const fn red() -> Self {
454        red()
455    }
456
457    /// The color green
458    pub const fn green() -> Self {
459        green()
460    }
461
462    /// The color blue
463    pub const fn blue() -> Self {
464        blue()
465    }
466
467    /// The color black
468    pub const fn black() -> Self {
469        black()
470    }
471
472    /// The color white
473    pub const fn white() -> Self {
474        white()
475    }
476
477    /// The color transparent black
478    pub const fn transparent_black() -> Self {
479        transparent_black()
480    }
481
482    /// Returns true if the HSLA color is fully transparent, false otherwise.
483    pub fn is_transparent(&self) -> bool {
484        self.a == 0.0
485    }
486
487    /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
488    ///
489    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
490    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
491    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
492    ///
493    /// Assumptions:
494    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
495    /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s  alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing its own alpha value.
496    /// - RGB color components are contained in the range [0, 1].
497    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
498    pub fn blend(self, other: Hsla) -> Hsla {
499        let alpha = other.a;
500
501        if alpha >= 1.0 {
502            other
503        } else if alpha <= 0.0 {
504            return self;
505        } else {
506            let converted_self = Rgba::from(self);
507            let converted_other = Rgba::from(other);
508            let blended_rgb = converted_self.blend(converted_other);
509            return Hsla::from(blended_rgb);
510        }
511    }
512
513    /// Returns a new HSLA color with the same hue, and lightness, but with no saturation.
514    pub fn grayscale(&self) -> Self {
515        Hsla {
516            h: self.h,
517            s: 0.,
518            l: self.l,
519            a: self.a,
520        }
521    }
522
523    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
524    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
525    pub fn fade_out(&mut self, factor: f32) {
526        self.a *= 1.0 - factor.clamp(0., 1.);
527    }
528
529    /// Multiplies the alpha value of the color by a given factor
530    /// and returns a new HSLA color.
531    ///
532    /// Useful for transforming colors with dynamic opacity,
533    /// like a color from an external source.
534    ///
535    /// Example:
536    /// ```
537    /// let color = gpui::red();
538    /// let faded_color = color.opacity(0.5);
539    /// assert_eq!(faded_color.a, 0.5);
540    /// ```
541    ///
542    /// This will return a red color with half the opacity.
543    ///
544    /// Example:
545    /// ```
546    /// let color = hlsa(0.7, 1.0, 0.5, 0.7); // A saturated blue
547    /// let faded_color = color.opacity(0.16);
548    /// assert_eq!(faded_color.a, 0.112);
549    /// ```
550    ///
551    /// This will return a blue color with around ~10% opacity,
552    /// suitable for an element's hover or selected state.
553    ///
554    pub fn opacity(&self, factor: f32) -> Self {
555        Hsla {
556            h: self.h,
557            s: self.s,
558            l: self.l,
559            a: self.a * factor.clamp(0., 1.),
560        }
561    }
562
563    /// Returns a new HSLA color with the same hue, saturation,
564    /// and lightness, but with a new alpha value.
565    ///
566    /// Example:
567    /// ```
568    /// let color = gpui::red();
569    /// let red_color = color.alpha(0.25);
570    /// assert_eq!(red_color.a, 0.25);
571    /// ```
572    ///
573    /// This will return a red color with half the opacity.
574    ///
575    /// Example:
576    /// ```
577    /// let color = hsla(0.7, 1.0, 0.5, 0.7); // A saturated blue
578    /// let faded_color = color.alpha(0.25);
579    /// assert_eq!(faded_color.a, 0.25);
580    /// ```
581    ///
582    /// This will return a blue color with 25% opacity.
583    pub fn alpha(&self, a: f32) -> Self {
584        Hsla {
585            h: self.h,
586            s: self.s,
587            l: self.l,
588            a: a.clamp(0., 1.),
589        }
590    }
591}
592
593impl From<Rgba> for Hsla {
594    fn from(color: Rgba) -> Self {
595        let r = color.r;
596        let g = color.g;
597        let b = color.b;
598
599        let max = r.max(g.max(b));
600        let min = r.min(g.min(b));
601        let delta = max - min;
602
603        let l = (max + min) / 2.0;
604        let s = if l == 0.0 || l == 1.0 {
605            0.0
606        } else if l < 0.5 {
607            delta / (2.0 * l)
608        } else {
609            delta / (2.0 - 2.0 * l)
610        };
611
612        let h = if delta == 0.0 {
613            0.0
614        } else if max == r {
615            ((g - b) / delta).rem_euclid(6.0) / 6.0
616        } else if max == g {
617            ((b - r) / delta + 2.0) / 6.0
618        } else {
619            ((r - g) / delta + 4.0) / 6.0
620        };
621
622        Hsla {
623            h,
624            s,
625            l,
626            a: color.a,
627        }
628    }
629}
630
631impl JsonSchema for Hsla {
632    fn schema_name() -> String {
633        Rgba::schema_name()
634    }
635
636    fn json_schema(generator: &mut SchemaGenerator) -> Schema {
637        Rgba::json_schema(generator)
638    }
639}
640
641impl Serialize for Hsla {
642    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
643    where
644        S: Serializer,
645    {
646        Rgba::from(*self).serialize(serializer)
647    }
648}
649
650impl<'de> Deserialize<'de> for Hsla {
651    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
652    where
653        D: Deserializer<'de>,
654    {
655        Ok(Rgba::deserialize(deserializer)?.into())
656    }
657}
658
659#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
660#[repr(C)]
661pub(crate) enum BackgroundTag {
662    Solid = 0,
663    LinearGradient = 1,
664    PatternSlash = 2,
665}
666
667/// A color space for color interpolation.
668///
669/// References:
670/// - <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method>
671/// - <https://www.w3.org/TR/css-color-4/#typedef-color-space>
672#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
673#[repr(C)]
674pub enum ColorSpace {
675    #[default]
676    /// The sRGB color space.
677    Srgb = 0,
678    /// The Oklab color space.
679    Oklab = 1,
680}
681
682impl Display for ColorSpace {
683    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
684        match self {
685            ColorSpace::Srgb => write!(f, "sRGB"),
686            ColorSpace::Oklab => write!(f, "Oklab"),
687        }
688    }
689}
690
691/// A background color, which can be either a solid color or a linear gradient.
692#[derive(Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
693#[repr(C)]
694pub struct Background {
695    pub(crate) tag: BackgroundTag,
696    pub(crate) color_space: ColorSpace,
697    pub(crate) solid: Hsla,
698    pub(crate) gradient_angle_or_pattern_height: f32,
699    pub(crate) colors: [LinearColorStop; 2],
700    /// Padding for alignment for repr(C) layout.
701    pad: u32,
702}
703
704impl std::fmt::Debug for Background {
705    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
706        match self.tag {
707            BackgroundTag::Solid => write!(f, "Solid({:?})", self.solid),
708            BackgroundTag::LinearGradient => {
709                write!(
710                    f,
711                    "LinearGradient({}, {:?}, {:?})",
712                    self.gradient_angle_or_pattern_height, self.colors[0], self.colors[1]
713                )
714            }
715            BackgroundTag::PatternSlash => {
716                write!(
717                    f,
718                    "PatternSlash({:?}, {})",
719                    self.solid, self.gradient_angle_or_pattern_height
720                )
721            }
722        }
723    }
724}
725
726impl Eq for Background {}
727impl Default for Background {
728    fn default() -> Self {
729        Self {
730            tag: BackgroundTag::Solid,
731            solid: Hsla::default(),
732            color_space: ColorSpace::default(),
733            gradient_angle_or_pattern_height: 0.0,
734            colors: [LinearColorStop::default(), LinearColorStop::default()],
735            pad: 0,
736        }
737    }
738}
739
740/// Creates a hash pattern background
741pub fn pattern_slash(color: Hsla, width: f32, interval: f32) -> Background {
742    let width_scaled = (width * 255.0) as u32;
743    let interval_scaled = (interval * 255.0) as u32;
744    let height = ((width_scaled * 0xFFFF) + interval_scaled) as f32;
745
746    Background {
747        tag: BackgroundTag::PatternSlash,
748        solid: color,
749        gradient_angle_or_pattern_height: height,
750        ..Default::default()
751    }
752}
753
754/// Creates a solid background color.
755pub fn solid_background(color: impl Into<Hsla>) -> Background {
756    Background {
757        solid: color.into(),
758        ..Default::default()
759    }
760}
761
762/// Creates a LinearGradient background color.
763///
764/// The gradient line's angle of direction. A value of `0.` is equivalent to top; increasing values rotate clockwise from there.
765///
766/// The `angle` is in degrees value in the range 0.0 to 360.0.
767///
768/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient>
769pub fn linear_gradient(
770    angle: f32,
771    from: impl Into<LinearColorStop>,
772    to: impl Into<LinearColorStop>,
773) -> Background {
774    Background {
775        tag: BackgroundTag::LinearGradient,
776        gradient_angle_or_pattern_height: angle,
777        colors: [from.into(), to.into()],
778        ..Default::default()
779    }
780}
781
782/// A color stop in a linear gradient.
783///
784/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient#linear-color-stop>
785#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
786#[repr(C)]
787pub struct LinearColorStop {
788    /// The color of the color stop.
789    pub color: Hsla,
790    /// The percentage of the gradient, in the range 0.0 to 1.0.
791    pub percentage: f32,
792}
793
794/// Creates a new linear color stop.
795///
796/// The percentage of the gradient, in the range 0.0 to 1.0.
797pub fn linear_color_stop(color: impl Into<Hsla>, percentage: f32) -> LinearColorStop {
798    LinearColorStop {
799        color: color.into(),
800        percentage,
801    }
802}
803
804impl LinearColorStop {
805    /// Returns a new color stop with the same color, but with a modified alpha value.
806    pub fn opacity(&self, factor: f32) -> Self {
807        Self {
808            percentage: self.percentage,
809            color: self.color.opacity(factor),
810        }
811    }
812}
813
814impl Background {
815    /// Use specified color space for color interpolation.
816    ///
817    /// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method>
818    pub fn color_space(mut self, color_space: ColorSpace) -> Self {
819        self.color_space = color_space;
820        self
821    }
822
823    /// Returns a new background color with the same hue, saturation, and lightness, but with a modified alpha value.
824    pub fn opacity(&self, factor: f32) -> Self {
825        let mut background = *self;
826        background.solid = background.solid.opacity(factor);
827        background.colors = [
828            self.colors[0].opacity(factor),
829            self.colors[1].opacity(factor),
830        ];
831        background
832    }
833
834    /// Returns whether the background color is transparent.
835    pub fn is_transparent(&self) -> bool {
836        match self.tag {
837            BackgroundTag::Solid => self.solid.is_transparent(),
838            BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()),
839            BackgroundTag::PatternSlash => self.solid.is_transparent(),
840        }
841    }
842}
843
844impl From<Hsla> for Background {
845    fn from(value: Hsla) -> Self {
846        Background {
847            tag: BackgroundTag::Solid,
848            solid: value,
849            ..Default::default()
850        }
851    }
852}
853impl From<Rgba> for Background {
854    fn from(value: Rgba) -> Self {
855        Background {
856            tag: BackgroundTag::Solid,
857            solid: Hsla::from(value),
858            ..Default::default()
859        }
860    }
861}
862
863#[cfg(test)]
864mod tests {
865    use serde_json::json;
866
867    use super::*;
868
869    #[test]
870    fn test_deserialize_three_value_hex_to_rgba() {
871        let actual: Rgba = serde_json::from_value(json!("#f09")).unwrap();
872
873        assert_eq!(actual, rgba(0xff0099ff))
874    }
875
876    #[test]
877    fn test_deserialize_four_value_hex_to_rgba() {
878        let actual: Rgba = serde_json::from_value(json!("#f09f")).unwrap();
879
880        assert_eq!(actual, rgba(0xff0099ff))
881    }
882
883    #[test]
884    fn test_deserialize_six_value_hex_to_rgba() {
885        let actual: Rgba = serde_json::from_value(json!("#ff0099")).unwrap();
886
887        assert_eq!(actual, rgba(0xff0099ff))
888    }
889
890    #[test]
891    fn test_deserialize_eight_value_hex_to_rgba() {
892        let actual: Rgba = serde_json::from_value(json!("#ff0099ff")).unwrap();
893
894        assert_eq!(actual, rgba(0xff0099ff))
895    }
896
897    #[test]
898    fn test_deserialize_eight_value_hex_with_padding_to_rgba() {
899        let actual: Rgba = serde_json::from_value(json!(" #f5f5f5ff   ")).unwrap();
900
901        assert_eq!(actual, rgba(0xf5f5f5ff))
902    }
903
904    #[test]
905    fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba() {
906        let actual: Rgba = serde_json::from_value(json!("#DeAdbEeF")).unwrap();
907
908        assert_eq!(actual, rgba(0xdeadbeef))
909    }
910
911    #[test]
912    fn test_background_solid() {
913        let color = Hsla::from(rgba(0xff0099ff));
914        let mut background = Background::from(color);
915        assert_eq!(background.tag, BackgroundTag::Solid);
916        assert_eq!(background.solid, color);
917
918        assert_eq!(background.opacity(0.5).solid, color.opacity(0.5));
919        assert_eq!(background.is_transparent(), false);
920        background.solid = hsla(0.0, 0.0, 0.0, 0.0);
921        assert_eq!(background.is_transparent(), true);
922    }
923
924    #[test]
925    fn test_background_linear_gradient() {
926        let from = linear_color_stop(rgba(0xff0099ff), 0.0);
927        let to = linear_color_stop(rgba(0x00ff99ff), 1.0);
928        let background = linear_gradient(90.0, from, to);
929        assert_eq!(background.tag, BackgroundTag::LinearGradient);
930        assert_eq!(background.colors[0], from);
931        assert_eq!(background.colors[1], to);
932
933        assert_eq!(background.opacity(0.5).colors[0], from.opacity(0.5));
934        assert_eq!(background.opacity(0.5).colors[1], to.opacity(0.5));
935        assert_eq!(background.is_transparent(), false);
936        assert_eq!(background.opacity(0.0).is_transparent(), true);
937    }
938}