Skip to main content

ui/
color.rs

1//! Color picking: a preset [`Swatch`] set the app configures, and
2//! [`ColorPicker`] for anything outside it.
3//!
4//! The set is read through [`crate::AppExt::color_swatches`] and replaced with
5//! [`crate::AppExt::set_color_swatches`]; until then it is [`default_swatches`].
6
7mod picker;
8
9use std::rc::Rc;
10
11use gpui::{App, Global, Hsla, SharedString};
12use theme::{Appearance, Theme};
13
14pub use picker::{ColorPicker, ColorPickerEvent, parse_hex, to_hex};
15
16/// One preset color, with a value per appearance.
17#[derive(Clone, Debug, PartialEq)]
18pub struct Swatch {
19    /// What a tooltip or an accessibility label calls it.
20    pub name: SharedString,
21    pub light: Hsla,
22    pub dark: Hsla,
23}
24
25impl Swatch {
26    pub fn new(
27        name: impl Into<SharedString>,
28        light: impl Into<Hsla>,
29        dark: impl Into<Hsla>,
30    ) -> Self {
31        Self {
32            name: name.into(),
33            light: light.into(),
34            dark: dark.into(),
35        }
36    }
37
38    /// The same color under both appearances.
39    pub fn fixed(name: impl Into<SharedString>, color: impl Into<Hsla>) -> Self {
40        let color = color.into();
41        Self::new(name, color, color)
42    }
43
44    /// The value for `theme`'s appearance.
45    pub fn resolve(&self, theme: &Theme) -> Hsla {
46        match theme.appearance {
47            Appearance::Light => self.light,
48            Appearance::Dark => self.dark,
49        }
50    }
51}
52
53/// One of Apple's macOS system colors.
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
55pub enum SystemColor {
56    Red,
57    Orange,
58    Yellow,
59    Green,
60    Mint,
61    Teal,
62    Cyan,
63    Blue,
64    Indigo,
65    Purple,
66    Pink,
67    Brown,
68}
69
70impl SystemColor {
71    /// Every one, red through brown.
72    pub const ALL: [Self; 12] = [
73        Self::Red,
74        Self::Orange,
75        Self::Yellow,
76        Self::Green,
77        Self::Mint,
78        Self::Teal,
79        Self::Cyan,
80        Self::Blue,
81        Self::Indigo,
82        Self::Purple,
83        Self::Pink,
84        Self::Brown,
85    ];
86
87    pub fn swatch(self) -> Swatch {
88        let (name, light, dark) = match self {
89            Self::Red => ("Red", 0xFF3B30, 0xFF453A),
90            Self::Orange => ("Orange", 0xFF9500, 0xFF9F0A),
91            Self::Yellow => ("Yellow", 0xFFCC00, 0xFFD60A),
92            Self::Green => ("Green", 0x28CD41, 0x32D74B),
93            Self::Mint => ("Mint", 0x00C7BE, 0x63E6E2),
94            Self::Teal => ("Teal", 0x59ADC4, 0x6AC4DC),
95            Self::Cyan => ("Cyan", 0x55BEF0, 0x5AC8F5),
96            Self::Blue => ("Blue", 0x007AFF, 0x0A84FF),
97            Self::Indigo => ("Indigo", 0x5856D6, 0x5E5CE6),
98            Self::Purple => ("Purple", 0xAF52DE, 0xBF5AF2),
99            Self::Pink => ("Pink", 0xFF2D55, 0xFF375F),
100            Self::Brown => ("Brown", 0xA2845E, 0xAC8E68),
101        };
102        Swatch::new(name, gpui::rgb(light), gpui::rgb(dark))
103    }
104
105    /// The value for `theme`'s appearance.
106    pub fn resolve(self, theme: &Theme) -> Hsla {
107        self.swatch().resolve(theme)
108    }
109
110    /// The name to store. Stable across releases; [`Self::from_name`] reads it
111    /// back.
112    pub fn name(self) -> &'static str {
113        match self {
114            Self::Red => "red",
115            Self::Orange => "orange",
116            Self::Yellow => "yellow",
117            Self::Green => "green",
118            Self::Mint => "mint",
119            Self::Teal => "teal",
120            Self::Cyan => "cyan",
121            Self::Blue => "blue",
122            Self::Indigo => "indigo",
123            Self::Purple => "purple",
124            Self::Pink => "pink",
125            Self::Brown => "brown",
126        }
127    }
128
129    pub fn from_name(name: &str) -> Option<Self> {
130        Self::ALL.into_iter().find(|color| color.name() == name)
131    }
132}
133
134/// Every [`SystemColor`], red through brown.
135pub fn default_swatches() -> Rc<[Swatch]> {
136    SystemColor::ALL.map(SystemColor::swatch).into()
137}
138
139struct Swatches(Rc<[Swatch]>);
140
141impl Global for Swatches {}
142
143pub(crate) fn swatches(cx: &App) -> Rc<[Swatch]> {
144    cx.try_global::<Swatches>()
145        .map_or_else(default_swatches, |set| set.0.clone())
146}
147
148pub(crate) fn set_swatches(set: impl Into<Rc<[Swatch]>>, cx: &mut App) {
149    cx.set_global(Swatches(set.into()));
150    cx.refresh_windows();
151}