Extract most colors in codebase into theme file. switch to dark

Max Brunsfeld created

Change summary

gpui/src/elements/label.rs        |  11 +
zed/assets/themes/dark.toml       |  38 +++++++
zed/assets/themes/light.toml      |  13 --
zed/languages/rust/highlights.scm |  20 +++
zed/src/editor.rs                 |   8 
zed/src/editor/element.rs         |  38 ++++--
zed/src/file_finder.rs            |  44 +++++--
zed/src/settings.rs               | 179 ++++++++++++++++++++++----------
zed/src/workspace.rs              |  10 
zed/src/workspace/pane.rs         |  44 ++++---
10 files changed, 275 insertions(+), 130 deletions(-)

Detailed changes

gpui/src/elements/label.rs 🔗

@@ -20,6 +20,7 @@ pub struct Label {
     family_id: FamilyId,
     font_properties: Properties,
     font_size: f32,
+    default_color: ColorU,
     highlights: Option<Highlights>,
 }
 
@@ -36,10 +37,16 @@ impl Label {
             family_id,
             font_properties: Properties::new(),
             font_size,
+            default_color: ColorU::black(),
             highlights: None,
         }
     }
 
+    pub fn with_default_color(mut self, color: ColorU) -> Self {
+        self.default_color = color;
+        self
+    }
+
     pub fn with_highlights(
         mut self,
         color: ColorU,
@@ -69,7 +76,7 @@ impl Label {
 
             for (char_ix, c) in self.text.char_indices() {
                 let mut font_id = font_id;
-                let mut color = ColorU::black();
+                let mut color = self.default_color;
                 if let Some(highlight_ix) = highlight_indices.peek() {
                     if char_ix == *highlight_ix {
                         font_id = highlight_font_id;
@@ -97,7 +104,7 @@ impl Label {
 
             runs
         } else {
-            smallvec![(self.text.len(), font_id, ColorU::black())]
+            smallvec![(self.text.len(), font_id, self.default_color)]
         }
     }
 }

zed/assets/themes/dark.toml 🔗

@@ -0,0 +1,38 @@
+[ui]
+tab_background = 0x131415
+tab_background_active = 0x1c1d1e
+tab_text = 0x5a5a5b
+tab_text_active = 0xffffff
+tab_border = 0x000000
+tab_icon_close = 0x383839
+tab_icon_dirty = 0x556de8
+tab_icon_conflict = 0xe45349
+modal_background = 0x3a3b3c
+modal_match_background = 0x424344
+modal_match_background_active = 0x094771
+modal_match_border = 0x000000
+modal_match_text = 0xcccccc
+modal_match_text_highlight = 0x18a3ff
+
+
+[editor]
+background = 0x1c1d1e
+gutter_background = 0x1c1d1e
+line_number = 0x5a5a5b
+line_number_active = 0xffffff
+default_text = 0xd4d4d4
+replicas = [
+    { selection = 0x264f78, cursor = 0xffffff },
+    { selection = 0x504f31, cursor = 0xfcf154 },
+]
+
+[syntax]
+keyword = 0xc586c0
+function = 0xdcdcaa
+string = 0xcb8f77
+type = 0x4ec9b0
+number = 0xb5cea8
+comment = 0x6a9955
+property = 0x4e94ce
+variant = 0x4fc1ff
+constant = 0x9cdcfe

zed/assets/themes/light.toml 🔗

@@ -1,13 +0,0 @@
-[ui]
-background = 0xffffff
-line_numbers = 0x237791
-text = 0x0d0d0d
-
-[syntax]
-keyword = 0xaf00db
-function = 0x795e26
-string = 0xa31515
-type = 0x267599
-number = 0x0d885b
-comment = 0x048204
-property = 0x001080

zed/languages/rust/highlights.scm 🔗

@@ -15,7 +15,27 @@
 (function_item name: (identifier) @function.definition)
 (function_signature_item name: (identifier) @function.definition)
 
+; Identifier conventions
+
+; Assume uppercase names are enum constructors
+((identifier) @variant
+ (#match? @variant "^[A-Z]"))
+
+; Assume that uppercase names in paths are types
+((scoped_identifier
+  path: (identifier) @type)
+ (#match? @type "^[A-Z]"))
+((scoped_identifier
+  path: (scoped_identifier
+    name: (identifier) @type))
+ (#match? @type "^[A-Z]"))
+
+; Assume all-caps names are constants
+((identifier) @constant
+ (#match? @constant "^[A-Z][A-Z\\d_]+$'"))
+
 [
+  "as"
   "async"
   "break"
   "const"

zed/src/editor.rs 🔗

@@ -2353,6 +2353,7 @@ impl Snapshot {
         viewport_height: f32,
         font_cache: &FontCache,
         layout_cache: &TextLayoutCache,
+        theme: &Theme,
     ) -> Result<Vec<Option<text_layout::Line>>> {
         let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?;
 
@@ -2378,7 +2379,7 @@ impl Snapshot {
                 layouts.push(Some(layout_cache.layout_str(
                     &line_number,
                     self.font_size,
-                    &[(line_number.len(), font_id, ColorU::black())],
+                    &[(line_number.len(), font_id, theme.editor.line_number.0)],
                 )));
             }
         }
@@ -2785,12 +2786,13 @@ mod tests {
         let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
 
         let settings = settings::channel(&font_cache).unwrap().1;
-        let (_, editor) = cx.add_window(|cx| Editor::for_buffer(buffer.clone(), settings, cx));
+        let (_, editor) =
+            cx.add_window(|cx| Editor::for_buffer(buffer.clone(), settings.clone(), cx));
 
         let layouts = editor.update(cx, |editor, cx| {
             editor
                 .snapshot(cx)
-                .layout_line_numbers(1000.0, &font_cache, &layout_cache)
+                .layout_line_numbers(1000.0, &font_cache, &layout_cache, &settings.borrow().theme)
                 .unwrap()
         });
         assert_eq!(layouts.len(), 6);

zed/src/editor/element.rs 🔗

@@ -1,6 +1,5 @@
-use crate::time::ReplicaId;
-
 use super::{DisplayPoint, Editor, SelectAction, Snapshot};
+use crate::time::ReplicaId;
 use gpui::{
     color::ColorU,
     geometry::{
@@ -184,12 +183,14 @@ impl EditorElement {
     }
 
     fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, cx: &mut PaintContext) {
+        let settings = self.view(cx.app).settings.borrow();
+        let theme = &settings.theme;
         let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
 
         cx.scene.push_layer(Some(rect));
         cx.scene.push_quad(Quad {
             bounds: rect,
-            background: Some(ColorU::white()),
+            background: Some(theme.editor.gutter_background.0),
             border: Border::new(0., ColorU::transparent_black()),
             corner_radius: 0.,
         });
@@ -214,6 +215,8 @@ impl EditorElement {
 
     fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, cx: &mut PaintContext) {
         let view = self.view(cx.app);
+        let settings = self.view(cx.app).settings.borrow();
+        let theme = &settings.theme.editor;
         let scroll_position = layout.snapshot.scroll_position();
         let start_row = scroll_position.y() as u32;
         let scroll_top = scroll_position.y() * layout.line_height;
@@ -224,26 +227,19 @@ impl EditorElement {
         cx.scene.push_layer(Some(bounds));
         cx.scene.push_quad(Quad {
             bounds,
-            background: Some(ColorU::white()),
+            background: Some(theme.background.0),
             border: Border::new(0., ColorU::transparent_black()),
             corner_radius: 0.,
         });
 
         // Draw selections
         let corner_radius = 2.5;
-        let colors = [
-            (ColorU::from_u32(0xa3d6ffff), ColorU::from_u32(0x000000ff)),
-            (ColorU::from_u32(0xffaf87ff), ColorU::from_u32(0xff8e72ff)),
-            (ColorU::from_u32(0x86eaccff), ColorU::from_u32(0x377771ff)),
-            (ColorU::from_u32(0xb8b8ffff), ColorU::from_u32(0x9381ffff)),
-            (ColorU::from_u32(0xf5cce8ff), ColorU::from_u32(0x4a2040ff)),
-        ];
         let mut cursors = SmallVec::<[Cursor; 32]>::new();
 
         let content_origin = bounds.origin() + layout.text_offset;
 
         for (replica_id, selections) in &layout.selections {
-            let (selection_color, cursor_color) = colors[*replica_id as usize % colors.len()];
+            let replica_theme = theme.replicas[*replica_id as usize % theme.replicas.len()];
 
             for selection in selections {
                 if selection.start != selection.end {
@@ -257,7 +253,7 @@ impl EditorElement {
                     };
 
                     let selection = Selection {
-                        color: selection_color,
+                        color: replica_theme.selection.0,
                         line_height: layout.line_height,
                         start_y: content_origin.y() + row_range.start as f32 * layout.line_height
                             - scroll_top,
@@ -300,7 +296,7 @@ impl EditorElement {
                             - scroll_left;
                         let y = selection.end.row() as f32 * layout.line_height - scroll_top;
                         cursors.push(Cursor {
-                            color: cursor_color,
+                            color: replica_theme.cursor.0,
                             origin: content_origin + vec2f(x, y),
                             line_height: layout.line_height,
                         });
@@ -392,7 +388,19 @@ impl Element for EditorElement {
         });
 
         let line_number_layouts = if snapshot.gutter_visible {
-            match snapshot.layout_line_numbers(size.y(), cx.font_cache, cx.text_layout_cache) {
+            let settings = self
+                .view
+                .upgrade(cx.app)
+                .unwrap()
+                .read(cx.app)
+                .settings
+                .borrow();
+            match snapshot.layout_line_numbers(
+                size.y(),
+                cx.font_cache,
+                cx.text_layout_cache,
+                &settings.theme,
+            ) {
                 Err(error) => {
                     log::error!("error laying out line numbers: {}", error);
                     return (size, None);

zed/src/file_finder.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     worktree::{match_paths, PathMatch, Worktree},
 };
 use gpui::{
-    color::{ColorF, ColorU},
+    color::ColorF,
     elements::*,
     fonts::{Properties, Weight},
     geometry::vector::vec2f,
@@ -69,6 +69,8 @@ impl View for FileFinder {
     }
 
     fn render(&self, _: &AppContext) -> ElementBox {
+        let settings = self.settings.borrow();
+
         Align::new(
             ConstrainedBox::new(
                 Container::new(
@@ -80,8 +82,8 @@ impl View for FileFinder {
                 .with_margin_top(12.0)
                 .with_uniform_padding(6.0)
                 .with_corner_radius(6.0)
-                .with_background_color(ColorU::from_u32(0xf2f2f2ff))
-                .with_shadow(vec2f(0., 4.), 12., ColorF::new(0.0, 0.0, 0.0, 0.25).to_u8())
+                .with_background_color(settings.theme.ui.modal_background)
+                .with_shadow(vec2f(0., 4.), 12., ColorF::new(0.0, 0.0, 0.0, 0.5).to_u8())
                 .boxed(),
             )
             .with_max_width(600.0)
@@ -113,6 +115,7 @@ impl FileFinder {
                     settings.ui_font_family,
                     settings.ui_font_size,
                 )
+                .with_default_color(settings.theme.editor.default_text.0)
                 .boxed(),
             )
             .with_margin_top(6.0)
@@ -136,8 +139,6 @@ impl FileFinder {
         );
 
         Container::new(list.boxed())
-            .with_background_color(ColorU::from_u32(0xf7f7f7ff))
-            .with_border(Border::all(1.0, ColorU::from_u32(0xdbdbdcff)))
             .with_margin_top(6.0)
             .named("matches")
     }
@@ -148,11 +149,12 @@ impl FileFinder {
         index: usize,
         cx: &AppContext,
     ) -> Option<ElementBox> {
+        let settings = self.settings.borrow();
+        let theme = &settings.theme.ui;
         self.labels_for_match(path_match, cx).map(
             |(file_name, file_name_positions, full_path, full_path_positions)| {
-                let settings = self.settings.borrow();
-                let highlight_color = ColorU::from_u32(0x304ee2ff);
                 let bold = *Properties::new().weight(Weight::BOLD);
+                let selected_index = self.selected_index();
                 let mut container = Container::new(
                     Flex::row()
                         .with_child(
@@ -177,7 +179,12 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_highlights(highlight_color, bold, file_name_positions)
+                                        .with_default_color(theme.modal_match_text.0)
+                                        .with_highlights(
+                                            theme.modal_match_text_highlight.0,
+                                            bold,
+                                            file_name_positions,
+                                        )
                                         .boxed(),
                                     )
                                     .with_child(
@@ -186,7 +193,12 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_highlights(highlight_color, bold, full_path_positions)
+                                        .with_default_color(theme.modal_match_text.0)
+                                        .with_highlights(
+                                            theme.modal_match_text_highlight.0,
+                                            bold,
+                                            full_path_positions,
+                                        )
                                         .boxed(),
                                     )
                                     .boxed(),
@@ -195,16 +207,16 @@ impl FileFinder {
                         )
                         .boxed(),
                 )
-                .with_uniform_padding(6.0);
+                .with_uniform_padding(6.0)
+                .with_background_color(if index == selected_index {
+                    theme.modal_match_background_active.0
+                } else {
+                    theme.modal_match_background.0
+                });
 
-                let selected_index = self.selected_index();
                 if index == selected_index || index < self.matches.len() - 1 {
                     container =
-                        container.with_border(Border::bottom(1.0, ColorU::from_u32(0xdbdbdcff)));
-                }
-
-                if index == selected_index {
-                    container = container.with_background_color(ColorU::from_u32(0xdbdbdcff));
+                        container.with_border(Border::bottom(1.0, theme.modal_match_border));
                 }
 
                 let entry = (path_match.tree_id, path_match.path.clone());

zed/src/settings.rs 🔗

@@ -7,7 +7,12 @@ use gpui::{
 };
 use postage::watch;
 use serde::Deserialize;
-use std::{collections::HashMap, sync::Arc};
+use std::{
+    collections::HashMap,
+    fmt,
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
 
 const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
 
@@ -23,12 +28,50 @@ pub struct Settings {
 
 #[derive(Clone, Default)]
 pub struct Theme {
-    pub background_color: ColorU,
-    pub line_number_color: ColorU,
-    pub default_text_color: ColorU,
-    syntax_styles: Vec<(String, ColorU, FontProperties)>,
+    pub ui: UiTheme,
+    pub editor: EditorTheme,
+    syntax: Vec<(String, ColorU, FontProperties)>,
+}
+
+#[derive(Clone, Default, Deserialize)]
+#[serde(default)]
+pub struct UiTheme {
+    pub tab_background: Color,
+    pub tab_background_active: Color,
+    pub tab_text: Color,
+    pub tab_text_active: Color,
+    pub tab_border: Color,
+    pub tab_icon_close: Color,
+    pub tab_icon_dirty: Color,
+    pub tab_icon_conflict: Color,
+    pub modal_background: Color,
+    pub modal_match_background: Color,
+    pub modal_match_background_active: Color,
+    pub modal_match_border: Color,
+    pub modal_match_text: Color,
+    pub modal_match_text_highlight: Color,
+}
+
+#[derive(Clone, Default, Deserialize)]
+#[serde(default)]
+pub struct EditorTheme {
+    pub background: Color,
+    pub gutter_background: Color,
+    pub line_number: Color,
+    pub line_number_active: Color,
+    pub default_text: Color,
+    pub replicas: Vec<ReplicaTheme>,
 }
 
+#[derive(Clone, Copy, Deserialize)]
+pub struct ReplicaTheme {
+    pub cursor: Color,
+    pub selection: Color,
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct Color(pub ColorU);
+
 #[derive(Clone, Debug)]
 pub struct ThemeMap(Arc<[StyleId]>);
 
@@ -44,7 +87,7 @@ impl Settings {
             ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?,
             ui_font_size: 12.0,
             theme: Arc::new(
-                Theme::parse(Assets::get("themes/light.toml").unwrap())
+                Theme::parse(Assets::get("themes/dark.toml").unwrap())
                     .expect("Failed to parse built-in theme"),
             ),
         })
@@ -61,17 +104,19 @@ impl Theme {
         #[derive(Deserialize)]
         struct ThemeToml {
             #[serde(default)]
-            syntax: HashMap<String, StyleToml>,
+            ui: UiTheme,
+            #[serde(default)]
+            editor: EditorTheme,
             #[serde(default)]
-            ui: HashMap<String, u32>,
+            syntax: HashMap<String, StyleToml>,
         }
 
         #[derive(Deserialize)]
         #[serde(untagged)]
         enum StyleToml {
-            Color(u32),
+            Color(Color),
             Full {
-                color: Option<u32>,
+                color: Option<Color>,
                 weight: Option<toml::Value>,
                 #[serde(default)]
                 italic: bool,
@@ -81,7 +126,7 @@ impl Theme {
         let theme_toml: ThemeToml =
             toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?;
 
-        let mut syntax_styles = Vec::<(String, ColorU, FontProperties)>::new();
+        let mut syntax = Vec::<(String, ColorU, FontProperties)>::new();
         for (key, style) in theme_toml.syntax {
             let (color, weight, italic) = match style {
                 StyleToml::Color(color) => (color, None, false),
@@ -89,55 +134,37 @@ impl Theme {
                     color,
                     weight,
                     italic,
-                } => (color.unwrap_or(0), weight, italic),
+                } => (color.unwrap_or(Color::default()), weight, italic),
             };
-            match syntax_styles.binary_search_by_key(&&key, |e| &e.0) {
+            match syntax.binary_search_by_key(&&key, |e| &e.0) {
                 Ok(i) | Err(i) => {
                     let mut properties = FontProperties::new();
                     properties.weight = deserialize_weight(weight)?;
                     if italic {
                         properties.style = FontStyle::Italic;
                     }
-                    syntax_styles.insert(i, (key, deserialize_color(color), properties));
+                    syntax.insert(i, (key, color.0, properties));
                 }
             }
         }
 
-        let background_color = theme_toml
-            .ui
-            .get("background")
-            .copied()
-            .map_or(ColorU::from_u32(0xffffffff), deserialize_color);
-        let line_number_color = theme_toml
-            .ui
-            .get("line_numbers")
-            .copied()
-            .map_or(ColorU::black(), deserialize_color);
-        let default_text_color = theme_toml
-            .ui
-            .get("text")
-            .copied()
-            .map_or(ColorU::black(), deserialize_color);
-
         Ok(Theme {
-            background_color,
-            line_number_color,
-            default_text_color,
-            syntax_styles,
+            ui: theme_toml.ui,
+            editor: theme_toml.editor,
+            syntax,
         })
     }
 
     pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) {
-        self.syntax_styles
-            .get(id.0 as usize)
-            .map_or((self.default_text_color, FontProperties::new()), |entry| {
-                (entry.1, entry.2)
-            })
+        self.syntax.get(id.0 as usize).map_or(
+            (self.editor.default_text.0, FontProperties::new()),
+            |entry| (entry.1, entry.2),
+        )
     }
 
     #[cfg(test)]
     pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> {
-        self.syntax_styles.get(id.0 as usize).map(|e| e.0.as_str())
+        self.syntax.get(id.0 as usize).map(|e| e.0.as_str())
     }
 }
 
@@ -151,7 +178,7 @@ impl ThemeMap {
                 .iter()
                 .map(|capture_name| {
                     theme
-                        .syntax_styles
+                        .syntax
                         .iter()
                         .enumerate()
                         .filter_map(|(i, (key, _, _))| {
@@ -193,16 +220,53 @@ impl Default for StyleId {
     }
 }
 
+impl<'de> Deserialize<'de> for Color {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let rgba_value = u32::deserialize(deserializer)?;
+        Ok(Self(ColorU::from_u32((rgba_value << 8) + 0xFF)))
+    }
+}
+
+impl Into<ColorU> for Color {
+    fn into(self) -> ColorU {
+        self.0
+    }
+}
+
+impl Deref for Color {
+    type Target = ColorU;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for Color {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl fmt::Debug for Color {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl PartialEq<ColorU> for Color {
+    fn eq(&self, other: &ColorU) -> bool {
+        self.0.eq(other)
+    }
+}
+
 pub fn channel(
     font_cache: &FontCache,
 ) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
     Ok(watch::channel_with(Settings::new(font_cache)?))
 }
 
-fn deserialize_color(color: u32) -> ColorU {
-    ColorU::from_u32((color << 8) + 0xFF)
-}
-
 fn deserialize_weight(weight: Option<toml::Value>) -> Result<FontWeight> {
     match &weight {
         None => return Ok(FontWeight::NORMAL),
@@ -228,8 +292,11 @@ mod tests {
         let theme = Theme::parse(
             r#"
             [ui]
+            tab_background_active = 0x100000
+
+            [editor]
             background = 0x00ed00
-            line_numbers = 0xdddddd
+            line_number = 0xdddddd
 
             [syntax]
             "beta.two" = 0xAABBCC
@@ -239,24 +306,25 @@ mod tests {
         )
         .unwrap();
 
-        assert_eq!(theme.background_color, ColorU::from_u32(0x00ED00FF));
-        assert_eq!(theme.line_number_color, ColorU::from_u32(0xddddddff));
+        assert_eq!(theme.ui.tab_background_active, ColorU::from_u32(0x100000ff));
+        assert_eq!(theme.editor.background, ColorU::from_u32(0x00ed00ff));
+        assert_eq!(theme.editor.line_number, ColorU::from_u32(0xddddddff));
         assert_eq!(
-            theme.syntax_styles,
+            theme.syntax,
             &[
                 (
                     "alpha.one".to_string(),
-                    ColorU::from_u32(0x112233FF),
+                    ColorU::from_u32(0x112233ff),
                     *FontProperties::new().weight(FontWeight::BOLD)
                 ),
                 (
                     "beta.two".to_string(),
-                    ColorU::from_u32(0xAABBCCFF),
+                    ColorU::from_u32(0xaabbccff),
                     *FontProperties::new().weight(FontWeight::NORMAL)
                 ),
                 (
                     "gamma.three".to_string(),
-                    ColorU::from_u32(0x000000FF),
+                    ColorU::from_u32(0x00000000),
                     *FontProperties::new()
                         .weight(FontWeight::LIGHT)
                         .style(FontStyle::Italic),
@@ -273,10 +341,9 @@ mod tests {
     #[test]
     fn test_theme_map() {
         let theme = Theme {
-            default_text_color: Default::default(),
-            background_color: ColorU::default(),
-            line_number_color: ColorU::default(),
-            syntax_styles: [
+            ui: Default::default(),
+            editor: Default::default(),
+            syntax: [
                 ("function", ColorU::from_u32(0x100000ff)),
                 ("function.method", ColorU::from_u32(0x200000ff)),
                 ("function.async", ColorU::from_u32(0x300000ff)),

zed/src/workspace.rs 🔗

@@ -12,9 +12,9 @@ use crate::{
 };
 use anyhow::{anyhow, Result};
 use gpui::{
-    color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
-    ClipboardItem, Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, Task,
-    View, ViewContext, ViewHandle, WeakModelHandle,
+    elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, ClipboardItem,
+    Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, Task, View,
+    ViewContext, ViewHandle, WeakModelHandle,
 };
 use log::error;
 pub use pane::*;
@@ -880,14 +880,14 @@ impl View for Workspace {
     }
 
     fn render(&self, _: &AppContext) -> ElementBox {
+        let settings = self.settings.borrow();
         Container::new(
-            // self.center.render(bump)
             Stack::new()
                 .with_child(self.center.render())
                 .with_children(self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()))
                 .boxed(),
         )
-        .with_background_color(rgbu(0xea, 0xea, 0xeb))
+        .with_background_color(settings.theme.editor.background)
         .named("workspace")
     }
 

zed/src/workspace/pane.rs 🔗

@@ -1,5 +1,5 @@
 use super::{ItemViewHandle, SplitDirection};
-use crate::settings::Settings;
+use crate::settings::{Settings, UiTheme};
 use gpui::{
     color::ColorU,
     elements::*,
@@ -180,7 +180,7 @@ impl Pane {
 
     fn render_tabs(&self, cx: &AppContext) -> ElementBox {
         let settings = self.settings.borrow();
-        let border_color = ColorU::from_u32(0xdbdbdcff);
+        let theme = &settings.theme.ui;
         let line_height = cx.font_cache().line_height(
             cx.font_cache().default_font(settings.ui_font_family),
             settings.ui_font_size,
@@ -189,6 +189,8 @@ impl Pane {
         let mut row = Flex::row();
         let last_item_ix = self.items.len() - 1;
         for (ix, item) in self.items.iter().enumerate() {
+            let is_active = ix == self.active_item;
+
             enum Tab {}
 
             row.add_child(
@@ -197,7 +199,7 @@ impl Pane {
                     MouseEventHandler::new::<Tab, _>(item.id(), cx, |mouse_state| {
                         let title = item.title(cx);
 
-                        let mut border = Border::new(1.0, border_color);
+                        let mut border = Border::new(1.0, theme.tab_border.0);
                         border.left = ix > 0;
                         border.right = ix == last_item_ix;
                         border.bottom = ix != self.active_item;
@@ -211,6 +213,11 @@ impl Pane {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
+                                        .with_default_color(if is_active {
+                                            theme.tab_text_active.0
+                                        } else {
+                                            theme.tab_text.0
+                                        })
                                         .boxed(),
                                     )
                                     .boxed(),
@@ -222,6 +229,7 @@ impl Pane {
                                         mouse_state.hovered,
                                         item.is_dirty(cx),
                                         item.has_conflict(cx),
+                                        theme,
                                         cx,
                                     ))
                                     .right()
@@ -232,13 +240,12 @@ impl Pane {
                         .with_horizontal_padding(10.)
                         .with_border(border);
 
-                        if ix == self.active_item {
+                        if is_active {
                             container = container
-                                .with_background_color(ColorU::white())
+                                .with_background_color(theme.tab_background_active)
                                 .with_padding_bottom(border.width);
                         } else {
-                            container =
-                                container.with_background_color(ColorU::from_u32(0xeaeaebff));
+                            container = container.with_background_color(theme.tab_background);
                         }
 
                         ConstrainedBox::new(
@@ -264,7 +271,7 @@ impl Pane {
         row.add_child(
             ConstrainedBox::new(
                 Container::new(Empty::new().boxed())
-                    .with_border(Border::bottom(1.0, border_color))
+                    .with_border(Border::bottom(1.0, theme.tab_border))
                     .boxed(),
             )
             .with_min_width(20.)
@@ -275,7 +282,7 @@ impl Pane {
             Expanded::new(
                 0.0,
                 Container::new(Empty::new().boxed())
-                    .with_border(Border::bottom(1.0, border_color))
+                    .with_border(Border::bottom(1.0, theme.tab_border))
                     .boxed(),
             )
             .named("filler"),
@@ -292,25 +299,25 @@ impl Pane {
         tab_hovered: bool,
         is_dirty: bool,
         has_conflict: bool,
+        theme: &UiTheme,
         cx: &AppContext,
     ) -> ElementBox {
         enum TabCloseButton {}
 
-        let dirty_color = ColorU::from_u32(0x556de8ff);
-        let conflict_color = ColorU::from_u32(0xe45349ff);
-        let mut clicked_color = dirty_color;
+        let mut clicked_color = theme.tab_icon_dirty;
         clicked_color.a = 180;
 
         let current_color = if has_conflict {
-            Some(conflict_color)
+            Some(theme.tab_icon_conflict)
         } else if is_dirty {
-            Some(dirty_color)
+            Some(theme.tab_icon_dirty)
         } else {
             None
         };
 
         let icon = if tab_hovered {
-            let mut icon = Svg::new("icons/x.svg");
+            let close_color = current_color.unwrap_or(theme.tab_icon_close).0;
+            let icon = Svg::new("icons/x.svg").with_color(close_color);
 
             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state| {
                 if mouse_state.hovered {
@@ -318,14 +325,11 @@ impl Pane {
                         .with_background_color(if mouse_state.clicked {
                             clicked_color
                         } else {
-                            dirty_color
+                            theme.tab_icon_dirty
                         })
                         .with_corner_radius(close_icon_size / 2.)
                         .boxed()
                 } else {
-                    if let Some(current_color) = current_color {
-                        icon = icon.with_color(current_color);
-                    }
                     icon.boxed()
                 }
             })
@@ -339,7 +343,7 @@ impl Pane {
                         let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
                         cx.scene.push_quad(Quad {
                             bounds: square,
-                            background: Some(current_color),
+                            background: Some(current_color.0),
                             border: Default::default(),
                             corner_radius: diameter / 2.,
                         });