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