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