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