scale.rs

  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}