color.rs

  1pub use crate::{theme, ButtonVariant, ElementExt, Theme};
  2use gpui2::{hsla, rgb, Hsla, WindowContext};
  3use strum::EnumIter;
  4
  5#[derive(Clone, Copy)]
  6pub struct PlayerThemeColors {
  7    pub cursor: Hsla,
  8    pub selection: Hsla,
  9}
 10
 11impl std::fmt::Debug for PlayerThemeColors {
 12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 13        f.debug_struct("PlayerThemeColors")
 14            .field("cursor", &self.cursor.to_rgb().to_hex())
 15            .field("selection", &self.selection.to_rgb().to_hex())
 16            .finish()
 17    }
 18}
 19
 20impl PlayerThemeColors {
 21    pub fn new(cx: &WindowContext, ix: usize) -> Self {
 22        let theme = theme(cx);
 23
 24        if ix < theme.players.len() {
 25            Self {
 26                cursor: theme.players[ix].cursor,
 27                selection: theme.players[ix].selection,
 28            }
 29        } else {
 30            Self {
 31                cursor: rgb::<Hsla>(0xff00ff),
 32                selection: rgb::<Hsla>(0xff00ff),
 33            }
 34        }
 35    }
 36}
 37
 38#[derive(Clone, Copy)]
 39pub struct SyntaxColor {
 40    pub comment: Hsla,
 41    pub string: Hsla,
 42    pub function: Hsla,
 43    pub keyword: Hsla,
 44}
 45
 46impl std::fmt::Debug for SyntaxColor {
 47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 48        f.debug_struct("SyntaxColor")
 49            .field("comment", &self.comment.to_rgb().to_hex())
 50            .field("string", &self.string.to_rgb().to_hex())
 51            .field("function", &self.function.to_rgb().to_hex())
 52            .field("keyword", &self.keyword.to_rgb().to_hex())
 53            .finish()
 54    }
 55}
 56
 57impl SyntaxColor {
 58    pub fn new(cx: &WindowContext) -> Self {
 59        let theme = theme(cx);
 60
 61        Self {
 62            comment: theme
 63                .syntax
 64                .get("comment")
 65                .cloned()
 66                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
 67            string: theme
 68                .syntax
 69                .get("string")
 70                .cloned()
 71                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
 72            function: theme
 73                .syntax
 74                .get("function")
 75                .cloned()
 76                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
 77            keyword: theme
 78                .syntax
 79                .get("keyword")
 80                .cloned()
 81                .unwrap_or_else(|| rgb::<Hsla>(0xff00ff)),
 82        }
 83    }
 84}
 85
 86/// ThemeColor is the primary interface for coloring elements in the UI.
 87///
 88/// It is a mapping layer between semantic theme colors and colors from the reference library.
 89///
 90/// While we are between zed and zed2 we use this to map semantic colors to the old theme.
 91#[derive(Clone, Copy)]
 92pub struct ThemeColor {
 93    pub transparent: Hsla,
 94    pub mac_os_traffic_light_red: Hsla,
 95    pub mac_os_traffic_light_yellow: Hsla,
 96    pub mac_os_traffic_light_green: Hsla,
 97    pub border: Hsla,
 98    pub border_variant: Hsla,
 99    pub border_focused: Hsla,
100    pub border_transparent: Hsla,
101    /// The background color of an elevated surface, like a modal, tooltip or toast.
102    pub elevated_surface: Hsla,
103    pub surface: Hsla,
104    /// Window background color of the base app
105    pub background: Hsla,
106    /// Default background for elements like filled buttons,
107    /// text fields, checkboxes, radio buttons, etc.
108    /// - TODO: Map to step 3.
109    pub filled_element: Hsla,
110    /// The background color of a hovered element, like a button being hovered
111    /// with a mouse, or hovered on a touch screen.
112    /// - TODO: Map to step 4.
113    pub filled_element_hover: Hsla,
114    /// The background color of an active element, like a button being pressed,
115    /// or tapped on a touch screen.
116    /// - TODO: Map to step 5.
117    pub filled_element_active: Hsla,
118    /// The background color of a selected element, like a selected tab,
119    /// a button toggled on, or a checkbox that is checked.
120    pub filled_element_selected: Hsla,
121    pub filled_element_disabled: Hsla,
122    pub ghost_element: Hsla,
123    /// The background color of a hovered element with no default background,
124    /// like a ghost-style button or an interactable list item.
125    /// - TODO: Map to step 3.
126    pub ghost_element_hover: Hsla,
127    /// - TODO: Map to step 4.
128    pub ghost_element_active: Hsla,
129    pub ghost_element_selected: Hsla,
130    pub ghost_element_disabled: Hsla,
131    pub text: Hsla,
132    pub text_muted: Hsla,
133    pub text_placeholder: Hsla,
134    pub text_disabled: Hsla,
135    pub text_accent: Hsla,
136    pub icon_muted: Hsla,
137    pub syntax: SyntaxColor,
138
139    pub status_bar: Hsla,
140    pub title_bar: Hsla,
141    pub toolbar: Hsla,
142    pub tab_bar: Hsla,
143    /// The background of the editor
144    pub editor: Hsla,
145    pub editor_subheader: Hsla,
146    pub editor_active_line: Hsla,
147    pub terminal: Hsla,
148    pub image_fallback_background: Hsla,
149
150    pub git_created: Hsla,
151    pub git_modified: Hsla,
152    pub git_deleted: Hsla,
153    pub git_conflict: Hsla,
154    pub git_ignored: Hsla,
155    pub git_renamed: Hsla,
156
157    pub player: [PlayerThemeColors; 8],
158}
159
160impl std::fmt::Debug for ThemeColor {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        dbg!("ThemeColor debug");
163
164        f.debug_struct("ThemeColor")
165            .field("transparent", &self.transparent.to_rgb().to_hex())
166            .field(
167                "mac_os_traffic_light_red",
168                &self.mac_os_traffic_light_red.to_rgb().to_hex(),
169            )
170            .field(
171                "mac_os_traffic_light_yellow",
172                &self.mac_os_traffic_light_yellow.to_rgb().to_hex(),
173            )
174            .field(
175                "mac_os_traffic_light_green",
176                &self.mac_os_traffic_light_green.to_rgb().to_hex(),
177            )
178            .field("border", &self.border.to_rgb().to_hex())
179            .field("border_variant", &self.border_variant.to_rgb().to_hex())
180            .field("border_focused", &self.border_focused.to_rgb().to_hex())
181            .field(
182                "border_transparent",
183                &self.border_transparent.to_rgb().to_hex(),
184            )
185            .field("elevated_surface", &self.elevated_surface.to_rgb().to_hex())
186            .field("surface", &self.surface.to_rgb().to_hex())
187            .field("background", &self.background.to_rgb().to_hex())
188            .field("filled_element", &self.filled_element.to_rgb().to_hex())
189            .field(
190                "filled_element_hover",
191                &self.filled_element_hover.to_rgb().to_hex(),
192            )
193            .field(
194                "filled_element_active",
195                &self.filled_element_active.to_rgb().to_hex(),
196            )
197            .field(
198                "filled_element_selected",
199                &self.filled_element_selected.to_rgb().to_hex(),
200            )
201            .field(
202                "filled_element_disabled",
203                &self.filled_element_disabled.to_rgb().to_hex(),
204            )
205            .field("ghost_element", &self.ghost_element.to_rgb().to_hex())
206            .field(
207                "ghost_element_hover",
208                &self.ghost_element_hover.to_rgb().to_hex(),
209            )
210            .field(
211                "ghost_element_active",
212                &self.ghost_element_active.to_rgb().to_hex(),
213            )
214            .field(
215                "ghost_element_selected",
216                &self.ghost_element_selected.to_rgb().to_hex(),
217            )
218            .field(
219                "ghost_element_disabled",
220                &self.ghost_element_disabled.to_rgb().to_hex(),
221            )
222            .field("text", &self.text.to_rgb().to_hex())
223            .field("text_muted", &self.text_muted.to_rgb().to_hex())
224            .field("text_placeholder", &self.text_placeholder.to_rgb().to_hex())
225            .field("text_disabled", &self.text_disabled.to_rgb().to_hex())
226            .field("text_accent", &self.text_accent.to_rgb().to_hex())
227            .field("icon_muted", &self.icon_muted.to_rgb().to_hex())
228            .field("syntax", &self.syntax)
229            .field("status_bar", &self.status_bar.to_rgb().to_hex())
230            .field("title_bar", &self.title_bar.to_rgb().to_hex())
231            .field("toolbar", &self.toolbar.to_rgb().to_hex())
232            .field("tab_bar", &self.tab_bar.to_rgb().to_hex())
233            .field("editor", &self.editor.to_rgb().to_hex())
234            .field("editor_subheader", &self.editor_subheader.to_rgb().to_hex())
235            .field(
236                "editor_active_line",
237                &self.editor_active_line.to_rgb().to_hex(),
238            )
239            .field("terminal", &self.terminal.to_rgb().to_hex())
240            .field(
241                "image_fallback_background",
242                &self.image_fallback_background.to_rgb().to_hex(),
243            )
244            .field("git_created", &self.git_created.to_rgb().to_hex())
245            .field("git_modified", &self.git_modified.to_rgb().to_hex())
246            .field("git_deleted", &self.git_deleted.to_rgb().to_hex())
247            .field("git_conflict", &self.git_conflict.to_rgb().to_hex())
248            .field("git_ignored", &self.git_ignored.to_rgb().to_hex())
249            .field("git_renamed", &self.git_renamed.to_rgb().to_hex())
250            .field("player", &self.player)
251            .finish()
252    }
253}
254
255impl ThemeColor {
256    pub fn new(cx: &WindowContext) -> Self {
257        let theme = theme(cx);
258        let transparent = hsla(0.0, 0.0, 0.0, 0.0);
259
260        let players = [
261            PlayerThemeColors::new(cx, 0),
262            PlayerThemeColors::new(cx, 1),
263            PlayerThemeColors::new(cx, 2),
264            PlayerThemeColors::new(cx, 3),
265            PlayerThemeColors::new(cx, 4),
266            PlayerThemeColors::new(cx, 5),
267            PlayerThemeColors::new(cx, 6),
268            PlayerThemeColors::new(cx, 7),
269        ];
270
271        Self {
272            transparent,
273            mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
274            mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
275            mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
276            border: theme.lowest.base.default.border,
277            border_variant: theme.lowest.variant.default.border,
278            border_focused: theme.lowest.accent.default.border,
279            border_transparent: transparent,
280            elevated_surface: theme.lowest.base.default.background,
281            surface: theme.middle.base.default.background,
282            background: theme.lowest.base.default.background,
283            filled_element: theme.lowest.base.default.background,
284            filled_element_hover: hsla(0.0, 0.0, 100.0, 0.12),
285            filled_element_active: hsla(0.0, 0.0, 100.0, 0.16),
286            filled_element_selected: theme.lowest.accent.default.background,
287            filled_element_disabled: transparent,
288            ghost_element: transparent,
289            ghost_element_hover: hsla(0.0, 0.0, 100.0, 0.08),
290            ghost_element_active: hsla(0.0, 0.0, 100.0, 0.12),
291            ghost_element_selected: theme.lowest.accent.default.background,
292            ghost_element_disabled: transparent,
293            text: theme.lowest.base.default.foreground,
294            text_muted: theme.lowest.variant.default.foreground,
295            /// TODO: map this to a real value
296            text_placeholder: theme.lowest.negative.default.foreground,
297            text_disabled: theme.lowest.base.disabled.foreground,
298            text_accent: theme.lowest.accent.default.foreground,
299            icon_muted: theme.lowest.variant.default.foreground,
300            syntax: SyntaxColor::new(cx),
301
302            status_bar: theme.lowest.base.default.background,
303            title_bar: theme.lowest.base.default.background,
304            toolbar: theme.highest.base.default.background,
305            tab_bar: theme.middle.base.default.background,
306            editor: theme.highest.base.default.background,
307            editor_subheader: theme.middle.base.default.background,
308            terminal: theme.highest.base.default.background,
309            editor_active_line: theme.highest.on.default.background,
310            image_fallback_background: theme.lowest.base.default.background,
311
312            git_created: theme.lowest.positive.default.foreground,
313            git_modified: theme.lowest.accent.default.foreground,
314            git_deleted: theme.lowest.negative.default.foreground,
315            git_conflict: theme.lowest.warning.default.foreground,
316            git_ignored: theme.lowest.base.disabled.foreground,
317            git_renamed: theme.lowest.warning.default.foreground,
318
319            player: players,
320        }
321    }
322}
323
324/// Colors used exclusively for syntax highlighting.
325///
326/// For now we deserialize these from a theme.
327/// These will be defined statically in the new theme.
328#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
329pub enum HighlightColor {
330    #[default]
331    Default,
332    Comment,
333    String,
334    Function,
335    Keyword,
336}
337
338impl HighlightColor {
339    pub fn hsla(&self, theme: &Theme) -> Hsla {
340        match self {
341            Self::Default => theme
342                .syntax
343                .get("primary")
344                .cloned()
345                .expect("Couldn't find `primary` in theme.syntax"),
346            Self::Comment => theme
347                .syntax
348                .get("comment")
349                .cloned()
350                .expect("Couldn't find `comment` in theme.syntax"),
351            Self::String => theme
352                .syntax
353                .get("string")
354                .cloned()
355                .expect("Couldn't find `string` in theme.syntax"),
356            Self::Function => theme
357                .syntax
358                .get("function")
359                .cloned()
360                .expect("Couldn't find `function` in theme.syntax"),
361            Self::Keyword => theme
362                .syntax
363                .get("keyword")
364                .cloned()
365                .expect("Couldn't find `keyword` in theme.syntax"),
366        }
367    }
368}