1use gpui::{AppContext, Hsla, SharedString};
2
3use crate::{ActiveTheme, Appearance};
4
5/// A one-based step in a [`ColorScale`].
6#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
7pub struct ColorScaleStep(usize);
8
9impl ColorScaleStep {
10 pub const ONE: Self = Self(1);
11 pub const TWO: Self = Self(2);
12 pub const THREE: Self = Self(3);
13 pub const FOUR: Self = Self(4);
14 pub const FIVE: Self = Self(5);
15 pub const SIX: Self = Self(6);
16 pub const SEVEN: Self = Self(7);
17 pub const EIGHT: Self = Self(8);
18 pub const NINE: Self = Self(9);
19 pub const TEN: Self = Self(10);
20 pub const ELEVEN: Self = Self(11);
21 pub const TWELVE: Self = Self(12);
22
23 /// All of the steps in a [`ColorScale`].
24 pub const ALL: [ColorScaleStep; 12] = [
25 Self::ONE,
26 Self::TWO,
27 Self::THREE,
28 Self::FOUR,
29 Self::FIVE,
30 Self::SIX,
31 Self::SEVEN,
32 Self::EIGHT,
33 Self::NINE,
34 Self::TEN,
35 Self::ELEVEN,
36 Self::TWELVE,
37 ];
38}
39
40pub struct ColorScale(Vec<Hsla>);
41
42impl FromIterator<Hsla> for ColorScale {
43 fn from_iter<T: IntoIterator<Item = Hsla>>(iter: T) -> Self {
44 Self(Vec::from_iter(iter))
45 }
46}
47
48impl ColorScale {
49 /// Returns the specified step in the [`ColorScale`].
50 #[inline]
51 pub fn step(&self, step: ColorScaleStep) -> Hsla {
52 // Steps are one-based, so we need convert to the zero-based vec index.
53 self.0[step.0 - 1]
54 }
55
56 /// `Step 1` - Used for main application backgrounds.
57 ///
58 /// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas.
59 ///
60 #[inline]
61 pub fn step_1(&self) -> Hsla {
62 self.step(ColorScaleStep::ONE)
63 }
64
65 /// `Step 2` - Used for both main application backgrounds and subtle component backgrounds.
66 ///
67 /// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds.
68 #[inline]
69 pub fn step_2(&self) -> Hsla {
70 self.step(ColorScaleStep::TWO)
71 }
72
73 /// `Step 3` - Used for UI component backgrounds in their normal states.
74 ///
75 /// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components.
76 #[inline]
77 pub fn step_3(&self) -> Hsla {
78 self.step(ColorScaleStep::THREE)
79 }
80
81 /// `Step 4` - Used for UI component backgrounds in their hover states.
82 ///
83 /// Also suited for pressed or selected states of components with a transparent background.
84 #[inline]
85 pub fn step_4(&self) -> Hsla {
86 self.step(ColorScaleStep::FOUR)
87 }
88
89 /// `Step 5` - Used for UI component backgrounds in their pressed or selected states.
90 #[inline]
91 pub fn step_5(&self) -> Hsla {
92 self.step(ColorScaleStep::FIVE)
93 }
94
95 /// `Step 6` - Used for subtle borders on non-interactive components.
96 ///
97 /// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators.
98 #[inline]
99 pub fn step_6(&self) -> Hsla {
100 self.step(ColorScaleStep::SIX)
101 }
102
103 /// `Step 7` - Used for subtle borders on interactive components.
104 ///
105 /// This step subtly delineates the boundary of elements users interact with.
106 #[inline]
107 pub fn step_7(&self) -> Hsla {
108 self.step(ColorScaleStep::SEVEN)
109 }
110
111 /// `Step 8` - Used for stronger borders on interactive components and focus rings.
112 ///
113 /// It strengthens the visibility and accessibility of active elements and their focus states.
114 #[inline]
115 pub fn step_8(&self) -> Hsla {
116 self.step(ColorScaleStep::EIGHT)
117 }
118
119 /// `Step 9` - Used for solid backgrounds.
120 ///
121 /// `Step 9` is the most saturated step, having the least mix of white or black.
122 ///
123 /// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as
124 /// error, warning, and success indicators.
125 #[inline]
126 pub fn step_9(&self) -> Hsla {
127 self.step(ColorScaleStep::NINE)
128 }
129
130 /// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state.
131 ///
132 /// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read.
133 #[inline]
134 pub fn step_10(&self) -> Hsla {
135 self.step(ColorScaleStep::TEN)
136 }
137
138 /// `Step 11` - Used for text and icons requiring low contrast or less emphasis.
139 #[inline]
140 pub fn step_11(&self) -> Hsla {
141 self.step(ColorScaleStep::ELEVEN)
142 }
143
144 /// `Step 12` - Used for text and icons requiring high contrast or prominence.
145 #[inline]
146 pub fn step_12(&self) -> Hsla {
147 self.step(ColorScaleStep::TWELVE)
148 }
149}
150
151pub struct ColorScales {
152 pub gray: ColorScaleSet,
153 pub mauve: ColorScaleSet,
154 pub slate: ColorScaleSet,
155 pub sage: ColorScaleSet,
156 pub olive: ColorScaleSet,
157 pub sand: ColorScaleSet,
158 pub gold: ColorScaleSet,
159 pub bronze: ColorScaleSet,
160 pub brown: ColorScaleSet,
161 pub yellow: ColorScaleSet,
162 pub amber: ColorScaleSet,
163 pub orange: ColorScaleSet,
164 pub tomato: ColorScaleSet,
165 pub red: ColorScaleSet,
166 pub ruby: ColorScaleSet,
167 pub crimson: ColorScaleSet,
168 pub pink: ColorScaleSet,
169 pub plum: ColorScaleSet,
170 pub purple: ColorScaleSet,
171 pub violet: ColorScaleSet,
172 pub iris: ColorScaleSet,
173 pub indigo: ColorScaleSet,
174 pub blue: ColorScaleSet,
175 pub cyan: ColorScaleSet,
176 pub teal: ColorScaleSet,
177 pub jade: ColorScaleSet,
178 pub green: ColorScaleSet,
179 pub grass: ColorScaleSet,
180 pub lime: ColorScaleSet,
181 pub mint: ColorScaleSet,
182 pub sky: ColorScaleSet,
183 pub black: ColorScaleSet,
184 pub white: ColorScaleSet,
185}
186
187impl IntoIterator for ColorScales {
188 type Item = ColorScaleSet;
189
190 type IntoIter = std::vec::IntoIter<Self::Item>;
191
192 fn into_iter(self) -> Self::IntoIter {
193 vec![
194 self.gray,
195 self.mauve,
196 self.slate,
197 self.sage,
198 self.olive,
199 self.sand,
200 self.gold,
201 self.bronze,
202 self.brown,
203 self.yellow,
204 self.amber,
205 self.orange,
206 self.tomato,
207 self.red,
208 self.ruby,
209 self.crimson,
210 self.pink,
211 self.plum,
212 self.purple,
213 self.violet,
214 self.iris,
215 self.indigo,
216 self.blue,
217 self.cyan,
218 self.teal,
219 self.jade,
220 self.green,
221 self.grass,
222 self.lime,
223 self.mint,
224 self.sky,
225 self.black,
226 self.white,
227 ]
228 .into_iter()
229 }
230}
231
232pub struct ColorScaleSet {
233 name: SharedString,
234 light: ColorScale,
235 dark: ColorScale,
236 light_alpha: ColorScale,
237 dark_alpha: ColorScale,
238}
239
240impl ColorScaleSet {
241 pub fn new(
242 name: impl Into<SharedString>,
243 light: ColorScale,
244 light_alpha: ColorScale,
245 dark: ColorScale,
246 dark_alpha: ColorScale,
247 ) -> Self {
248 Self {
249 name: name.into(),
250 light,
251 light_alpha,
252 dark,
253 dark_alpha,
254 }
255 }
256
257 pub fn name(&self) -> &SharedString {
258 &self.name
259 }
260
261 pub fn light(&self) -> &ColorScale {
262 &self.light
263 }
264
265 pub fn light_alpha(&self) -> &ColorScale {
266 &self.light_alpha
267 }
268
269 pub fn dark(&self) -> &ColorScale {
270 &self.dark
271 }
272
273 pub fn dark_alpha(&self) -> &ColorScale {
274 &self.dark_alpha
275 }
276
277 pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla {
278 match cx.theme().appearance {
279 Appearance::Light => self.light().step(step),
280 Appearance::Dark => self.dark().step(step),
281 }
282 }
283
284 pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla {
285 match cx.theme().appearance {
286 Appearance::Light => self.light_alpha.step(step),
287 Appearance::Dark => self.dark_alpha.step(step),
288 }
289 }
290}