Restructure Theme with new style objects

Max Brunsfeld created

Change summary

gpui/src/elements/container.rs | 30 ++++++++--
gpui/src/elements/label.rs     |  4 +
gpui/src/scene.rs              | 10 +++
zed/assets/themes/_base.toml   | 47 ++++++++++++-----
zed/src/file_finder.rs         | 37 +++----------
zed/src/lib.rs                 |  1 
zed/src/settings.rs            | 96 ++++++++---------------------------
zed/src/theme.rs               | 80 ++++++++++++++++++++++++++++++
zed/src/theme_selector.rs      | 39 ++++---------
zed/src/workspace/pane.rs      | 43 +++++++++-------
10 files changed, 218 insertions(+), 169 deletions(-)

Detailed changes

gpui/src/elements/container.rs 🔗

@@ -16,12 +16,18 @@ use crate::{
 
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct ContainerStyle {
-    margin: Margin,
-    padding: Padding,
-    background_color: Option<Color>,
-    border: Border,
-    corner_radius: f32,
-    shadow: Option<Shadow>,
+    #[serde(default)]
+    pub margin: Margin,
+    #[serde(default)]
+    pub padding: Padding,
+    #[serde(rename = "background")]
+    pub background_color: Option<Color>,
+    #[serde(default)]
+    pub border: Border,
+    #[serde(default)]
+    pub corner_radius: f32,
+    #[serde(default)]
+    pub shadow: Option<Shadow>,
 }
 
 pub struct Container {
@@ -247,9 +253,13 @@ impl ToJson for ContainerStyle {
 
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct Margin {
+    #[serde(default)]
     top: f32,
+    #[serde(default)]
     left: f32,
+    #[serde(default)]
     bottom: f32,
+    #[serde(default)]
     right: f32,
 }
 
@@ -274,9 +284,13 @@ impl ToJson for Margin {
 
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct Padding {
+    #[serde(default)]
     top: f32,
+    #[serde(default)]
     left: f32,
+    #[serde(default)]
     bottom: f32,
+    #[serde(default)]
     right: f32,
 }
 
@@ -301,9 +315,11 @@ impl ToJson for Padding {
 
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct Shadow {
-    #[serde(deserialize_with = "deserialize_vec2f")]
+    #[serde(default, deserialize_with = "deserialize_vec2f")]
     offset: Vector2F,
+    #[serde(default)]
     blur: f32,
+    #[serde(default)]
     color: Color,
 }
 

gpui/src/elements/label.rs 🔗

@@ -25,9 +25,11 @@ pub struct Label {
 
 #[derive(Clone, Debug, Default, Deserialize)]
 pub struct LabelStyle {
+    #[serde(default = "Color::black")]
     pub color: Color,
+    #[serde(default)]
     pub highlight_color: Option<Color>,
-    #[serde(deserialize_with = "deserialize_font_properties")]
+    #[serde(default, deserialize_with = "deserialize_font_properties")]
     pub font_properties: Properties,
     #[serde(default, deserialize_with = "deserialize_option_font_properties")]
     pub highlight_font_properties: Option<Properties>,

gpui/src/scene.rs 🔗

@@ -59,14 +59,24 @@ pub struct Icon {
 
 #[derive(Clone, Copy, Default, Debug, Deserialize)]
 pub struct Border {
+    #[serde(default = "default_border_width")]
     pub width: f32,
+    #[serde(default)]
     pub color: Option<Color>,
+    #[serde(default)]
     pub top: bool,
+    #[serde(default)]
     pub right: bool,
+    #[serde(default)]
     pub bottom: bool,
+    #[serde(default)]
     pub left: bool,
 }
 
+fn default_border_width() -> f32 {
+    1.0
+}
+
 #[derive(Debug)]
 pub struct Path {
     pub bounds: RectF,

zed/assets/themes/_base.toml 🔗

@@ -1,19 +1,36 @@
 [ui]
 background = "$elevation_1"
-tab_background = "$elevation_2"
-tab_background_active = "$elevation_3"
-tab_text = "$text_dull"
-tab_text_active = "$text_bright"
-tab_border = 0x000000
-tab_icon_close = 0x383839
-tab_icon_dirty = 0x556de8
-tab_icon_conflict = 0xe45349
-modal_background = "$elevation_4"
-modal_match_background = 0x424344
-modal_match_background_active = 0x094771
-modal_match_border = 0x000000
-modal_match_text = 0xcccccc
-modal_match_text_highlight = 0x18a3ff
+
+[ui.tab]
+background = "$elevation_2"
+color = "$text_dull"
+border.color = 0x000000
+icon_close = 0x383839
+icon_dirty = 0x556de8
+icon_conflict = 0xe45349
+
+[ui.active_tab]
+extends = ".."
+background = "$elevation_3"
+color = "$text_bright"
+
+[ui.selector]
+background = "$elevation_4"
+padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 }
+margin.top = 12.0
+corner_radius = 6.0
+shadow = { offset = [0.0, 0.0], blur = 12.0, color = 0x00000088 }
+
+[ui.selector.item]
+background = 0x424344
+text = 0xcccccc
+highlight_text = 0x18a3ff
+highlight_font_properties = { weight = "bold" }
+border = { color = 0x000000, width = 1.0 }
+
+[ui.selector.active_item]
+extends = ".."
+background = 0x094771
 
 [editor]
 background = "$elevation_3"
@@ -21,7 +38,7 @@ gutter_background = "$elevation_3"
 active_line_background = "$elevation_4"
 line_number = "$text_dull"
 line_number_active = "$text_bright"
-default_text = "$text_normal"
+text = "$text_normal"
 replicas = [
     { selection = 0x264f78, cursor = "$text_bright" },
     { selection = 0x504f31, cursor = 0xfcf154 },

zed/src/file_finder.rs 🔗

@@ -6,12 +6,9 @@ use crate::{
     worktree::{match_paths, PathMatch, Worktree},
 };
 use gpui::{
-    color::Color,
     elements::*,
-    fonts::{Properties, Weight},
-    geometry::vector::vec2f,
     keymap::{self, Binding},
-    AppContext, Axis, Border, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
+    AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
     ViewHandle, WeakViewHandle,
 };
 use postage::watch;
@@ -78,11 +75,7 @@ impl View for FileFinder {
                         .with_child(Expanded::new(1.0, self.render_matches()).boxed())
                         .boxed(),
                 )
-                .with_margin_top(12.0)
-                .with_uniform_padding(6.0)
-                .with_corner_radius(6.0)
-                .with_background_color(settings.theme.ui.modal_background)
-                .with_shadow(vec2f(0., 4.), 12., Color::new(0, 0, 0, 128))
+                .with_style(&settings.theme.ui.selector.container)
                 .boxed(),
             )
             .with_max_width(600.0)
@@ -114,7 +107,7 @@ impl FileFinder {
                     settings.ui_font_family,
                     settings.ui_font_size,
                 )
-                .with_default_color(settings.theme.editor.default_text)
+                .with_default_color(settings.theme.editor.text)
                 .boxed(),
             )
             .with_margin_top(6.0)
@@ -152,15 +145,8 @@ impl FileFinder {
         let theme = &settings.theme.ui;
         self.labels_for_match(path_match, cx).map(
             |(file_name, file_name_positions, full_path, full_path_positions)| {
-                let bold = *Properties::new().weight(Weight::BOLD);
                 let selected_index = self.selected_index();
-                let label_style = LabelStyle {
-                    color: theme.modal_match_text,
-                    highlight_color: Some(theme.modal_match_text_highlight),
-                    highlight_font_properties: Some(bold),
-                    ..Default::default()
-                };
-                let mut container = Container::new(
+                let container = Container::new(
                     Flex::row()
                         .with_child(
                             Container::new(
@@ -184,7 +170,7 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_style(&label_style)
+                                        .with_style(&theme.selector.label)
                                         .with_highlights(file_name_positions)
                                         .boxed(),
                                     )
@@ -194,7 +180,7 @@ impl FileFinder {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_style(&label_style)
+                                        .with_style(&theme.selector.label)
                                         .with_highlights(full_path_positions)
                                         .boxed(),
                                     )
@@ -205,17 +191,12 @@ impl FileFinder {
                         .boxed(),
                 )
                 .with_uniform_padding(6.0)
-                .with_background_color(if index == selected_index {
-                    theme.modal_match_background_active
+                .with_style(if index == selected_index {
+                    &theme.selector.active_item.container
                 } else {
-                    theme.modal_match_background
+                    &theme.selector.item.container
                 });
 
-                if index == selected_index || index < self.matches.len() - 1 {
-                    container =
-                        container.with_border(Border::bottom(1.0, theme.modal_match_border));
-                }
-
                 let entry = (path_match.tree_id, path_match.path.clone());
                 EventHandler::new(container.boxed())
                     .on_mouse_down(move |cx| {

zed/src/lib.rs 🔗

@@ -10,6 +10,7 @@ pub mod settings;
 mod sum_tree;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
+pub mod theme;
 pub mod theme_selector;
 mod time;
 mod util;

zed/src/settings.rs 🔗

@@ -11,6 +11,9 @@ use serde::{de::value::MapDeserializer, Deserialize};
 use serde_json::Value;
 use std::{collections::HashMap, sync::Arc};
 
+use crate::theme;
+pub use theme::Theme;
+
 const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
 
 #[derive(Clone)]
@@ -29,13 +32,6 @@ pub struct ThemeRegistry {
     theme_data: Mutex<HashMap<String, Arc<ThemeToml>>>,
 }
 
-#[derive(Clone, Default)]
-pub struct Theme {
-    pub ui: UiTheme,
-    pub editor: EditorTheme,
-    pub syntax: Vec<(String, Color, FontProperties)>,
-}
-
 #[derive(Deserialize)]
 struct ThemeToml {
     #[serde(default)]
@@ -50,44 +46,6 @@ struct ThemeToml {
     syntax: HashMap<String, Value>,
 }
 
-#[derive(Clone, Default, Deserialize)]
-#[serde(default)]
-pub struct UiTheme {
-    pub background: Color,
-    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, Deserialize)]
-#[serde(default)]
-pub struct EditorTheme {
-    pub background: Color,
-    pub gutter_background: Color,
-    pub active_line_background: Color,
-    pub line_number: Color,
-    pub line_number_active: Color,
-    pub default_text: Color,
-    pub replicas: Vec<ReplicaTheme>,
-}
-
-#[derive(Clone, Copy, Deserialize, Default)]
-pub struct ReplicaTheme {
-    pub cursor: Color,
-    pub selection: Color,
-}
-
 #[derive(Clone, Debug)]
 pub struct ThemeMap(Arc<[StyleId]>);
 
@@ -169,8 +127,8 @@ impl ThemeRegistry {
         }
 
         let theme = Arc::new(Theme {
-            ui: UiTheme::deserialize(MapDeserializer::new(theme_toml.ui.clone().into_iter()))?,
-            editor: EditorTheme::deserialize(MapDeserializer::new(
+            ui: theme::Ui::deserialize(MapDeserializer::new(theme_toml.ui.clone().into_iter()))?,
+            editor: theme::Editor::deserialize(MapDeserializer::new(
                 theme_toml.editor.clone().into_iter(),
             ))?,
             syntax,
@@ -229,7 +187,7 @@ impl Theme {
     pub fn syntax_style(&self, id: StyleId) -> (Color, FontProperties) {
         self.syntax
             .get(id.0 as usize)
-            .map_or((self.editor.default_text, FontProperties::new()), |entry| {
+            .map_or((self.editor.text, FontProperties::new()), |entry| {
                 (entry.1, entry.2)
             })
     }
@@ -240,20 +198,6 @@ impl Theme {
     }
 }
 
-impl Default for EditorTheme {
-    fn default() -> Self {
-        Self {
-            background: Default::default(),
-            gutter_background: Default::default(),
-            active_line_background: Default::default(),
-            line_number: Default::default(),
-            line_number_active: Default::default(),
-            default_text: Default::default(),
-            replicas: vec![ReplicaTheme::default()],
-        }
-    }
-}
-
 impl ThemeMap {
     pub fn new(capture_names: &[String], theme: &Theme) -> Self {
         // For each capture name in the highlight query, find the longest
@@ -407,8 +351,8 @@ mod tests {
         let assets = TestAssets(&[(
             "themes/my-theme.toml",
             r#"
-            [ui]
-            tab_background_active = 0x100000
+            [ui.tab.active]
+            background = 0x100000
 
             [editor]
             background = 0x00ed00
@@ -424,7 +368,10 @@ mod tests {
         let registry = ThemeRegistry::new(assets);
         let theme = registry.get("my-theme").unwrap();
 
-        assert_eq!(theme.ui.tab_background_active, Color::from_u32(0x100000ff));
+        assert_eq!(
+            theme.ui.active_tab.container.background_color,
+            Some(Color::from_u32(0x100000ff))
+        );
         assert_eq!(theme.editor.background, Color::from_u32(0x00ed00ff));
         assert_eq!(theme.editor.line_number, Color::from_u32(0xddddddff));
         assert_eq!(
@@ -459,9 +406,9 @@ mod tests {
                 r#"
                 abstract = true
 
-                [ui]
-                tab_background = 0x111111
-                tab_text = "$variable_1"
+                [ui.tab]
+                background = 0x111111
+                text = "$variable_1"
 
                 [editor]
                 background = 0x222222
@@ -477,8 +424,8 @@ mod tests {
                 variable_1 = 0x333333
                 variable_2 = 0x444444
 
-                [ui]
-                tab_background = 0x555555
+                [ui.tab]
+                background = 0x555555
 
                 [editor]
                 background = 0x666666
@@ -499,10 +446,13 @@ mod tests {
         let registry = ThemeRegistry::new(assets);
         let theme = registry.get("light").unwrap();
 
-        assert_eq!(theme.ui.tab_background, Color::from_u32(0x555555ff));
-        assert_eq!(theme.ui.tab_text, Color::from_u32(0x333333ff));
+        assert_eq!(
+            theme.ui.tab.container.background_color,
+            Some(Color::from_u32(0x555555ff))
+        );
+        assert_eq!(theme.ui.tab.label.color, Color::from_u32(0x333333ff));
         assert_eq!(theme.editor.background, Color::from_u32(0x666666ff));
-        assert_eq!(theme.editor.default_text, Color::from_u32(0x444444ff));
+        assert_eq!(theme.editor.text, Color::from_u32(0x444444ff));
 
         assert_eq!(
             registry.list().collect::<Vec<_>>(),

zed/src/theme.rs 🔗

@@ -0,0 +1,80 @@
+use gpui::color::Color;
+use gpui::elements::{ContainerStyle, LabelStyle};
+use gpui::fonts::Properties as FontProperties;
+use serde::Deserialize;
+
+#[derive(Debug, Default)]
+pub struct Theme {
+    pub ui: Ui,
+    pub editor: Editor,
+    pub syntax: Vec<(String, Color, FontProperties)>,
+}
+
+#[derive(Debug, Default, Deserialize)]
+pub struct Ui {
+    pub background: Color,
+    pub tab: Tab,
+    pub active_tab: Tab,
+    pub selector: Selector,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Editor {
+    pub background: Color,
+    pub gutter_background: Color,
+    pub active_line_background: Color,
+    pub line_number: Color,
+    pub line_number_active: Color,
+    pub text: Color,
+    pub replicas: Vec<Replica>,
+}
+
+#[derive(Clone, Copy, Debug, Default, Deserialize)]
+pub struct Replica {
+    pub cursor: Color,
+    pub selection: Color,
+}
+
+#[derive(Debug, Default, Deserialize)]
+pub struct Tab {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+    pub icon_close: Color,
+    pub icon_dirty: Color,
+    pub icon_conflict: Color,
+}
+
+#[derive(Debug, Default, Deserialize)]
+pub struct Selector {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+
+    pub item: SelectorItem,
+    pub active_item: SelectorItem,
+}
+
+#[derive(Debug, Default, Deserialize)]
+pub struct SelectorItem {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub label: LabelStyle,
+}
+
+impl Default for Editor {
+    fn default() -> Self {
+        Self {
+            background: Default::default(),
+            gutter_background: Default::default(),
+            active_line_background: Default::default(),
+            line_number: Default::default(),
+            line_number_active: Default::default(),
+            text: Default::default(),
+            replicas: vec![Replica::default()],
+        }
+    }
+}

zed/src/theme_selector.rs 🔗

@@ -9,15 +9,12 @@ use crate::{
 };
 use futures::lock::Mutex;
 use gpui::{
-    color::Color,
     elements::{
-        Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, LabelStyle,
-        ParentElement, UniformList, UniformListState,
+        Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, ParentElement,
+        UniformList, UniformListState,
     },
-    fonts::{Properties, Weight},
-    geometry::vector::vec2f,
     keymap::{self, Binding},
-    AppContext, Axis, Border, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
+    AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
     ViewContext, ViewHandle,
 };
 use postage::watch;
@@ -199,7 +196,7 @@ impl ThemeSelector {
                     settings.ui_font_family,
                     settings.ui_font_size,
                 )
-                .with_default_color(settings.theme.editor.default_text)
+                .with_default_color(settings.theme.editor.text)
                 .boxed(),
             )
             .with_margin_top(6.0)
@@ -234,32 +231,26 @@ impl ThemeSelector {
         let settings = self.settings.borrow();
         let theme = &settings.theme.ui;
 
-        let mut container = Container::new(
+        let container = Container::new(
             Label::new(
                 theme_match.string.clone(),
                 settings.ui_font_family,
                 settings.ui_font_size,
             )
-            .with_style(&LabelStyle {
-                color: theme.modal_match_text,
-                highlight_color: Some(theme.modal_match_text_highlight),
-                highlight_font_properties: Some(*Properties::new().weight(Weight::BOLD)),
-                ..Default::default()
+            .with_style(if index == self.selected_index {
+                &theme.selector.active_item.label
+            } else {
+                &theme.selector.item.label
             })
             .with_highlights(theme_match.positions.clone())
             .boxed(),
         )
-        .with_uniform_padding(6.0)
-        .with_background_color(if index == self.selected_index {
-            theme.modal_match_background_active
+        .with_style(if index == self.selected_index {
+            &theme.selector.active_item.container
         } else {
-            theme.modal_match_background
+            &theme.selector.item.container
         });
 
-        if index == self.selected_index || index < self.matches.len() - 1 {
-            container = container.with_border(Border::bottom(1.0, theme.modal_match_border));
-        }
-
         container.boxed()
     }
 }
@@ -284,11 +275,7 @@ impl View for ThemeSelector {
                         .with_child(Expanded::new(1.0, self.render_matches(cx)).boxed())
                         .boxed(),
                 )
-                .with_margin_top(12.0)
-                .with_uniform_padding(6.0)
-                .with_corner_radius(6.0)
-                .with_background_color(settings.theme.ui.modal_background)
-                .with_shadow(vec2f(0., 4.), 12., Color::new(0, 0, 0, 128))
+                .with_style(&settings.theme.ui.selector.container)
                 .boxed(),
             )
             .with_max_width(600.0)

zed/src/workspace/pane.rs 🔗

@@ -1,5 +1,5 @@
 use super::{ItemViewHandle, SplitDirection};
-use crate::settings::{Settings, UiTheme};
+use crate::{settings::Settings, theme};
 use gpui::{
     color::Color,
     elements::*,
@@ -193,6 +193,7 @@ impl Pane {
             let is_active = ix == self.active_item;
 
             enum Tab {}
+            let border = &theme.tab.container.border;
 
             row.add_child(
                 Expanded::new(
@@ -200,10 +201,10 @@ impl Pane {
                     MouseEventHandler::new::<Tab, _>(item.id(), cx, |mouse_state| {
                         let title = item.title(cx);
 
-                        let mut border = Border::new(1.0, theme.tab_border);
+                        let mut border = border.clone();
                         border.left = ix > 0;
                         border.right = ix == last_item_ix;
-                        border.bottom = ix != self.active_item;
+                        border.bottom = !is_active;
 
                         let mut container = Container::new(
                             Stack::new()
@@ -214,10 +215,10 @@ impl Pane {
                                             settings.ui_font_family,
                                             settings.ui_font_size,
                                         )
-                                        .with_default_color(if is_active {
-                                            theme.tab_text_active
+                                        .with_style(if is_active {
+                                            &theme.active_tab.label
                                         } else {
-                                            theme.tab_text
+                                            &theme.tab.label
                                         })
                                         .boxed(),
                                     )
@@ -238,15 +239,16 @@ impl Pane {
                                 )
                                 .boxed(),
                         )
+                        .with_style(if is_active {
+                            &theme.active_tab.container
+                        } else {
+                            &theme.tab.container
+                        })
                         .with_horizontal_padding(10.)
                         .with_border(border);
 
                         if is_active {
-                            container = container
-                                .with_background_color(theme.tab_background_active)
-                                .with_padding_bottom(border.width);
-                        } else {
-                            container = container.with_background_color(theme.tab_background);
+                            container = container.with_padding_bottom(border.width);
                         }
 
                         ConstrainedBox::new(
@@ -269,10 +271,13 @@ impl Pane {
 
         // Ensure there's always a minimum amount of space after the last tab,
         // so that the tab's border doesn't abut the window's border.
+        let mut border = Border::bottom(1.0, Color::default());
+        border.color = theme.tab.container.border.color;
+
         row.add_child(
             ConstrainedBox::new(
                 Container::new(Empty::new().boxed())
-                    .with_border(Border::bottom(1.0, theme.tab_border))
+                    .with_border(border)
                     .boxed(),
             )
             .with_min_width(20.)
@@ -283,7 +288,7 @@ impl Pane {
             Expanded::new(
                 0.0,
                 Container::new(Empty::new().boxed())
-                    .with_border(Border::bottom(1.0, theme.tab_border))
+                    .with_border(border)
                     .boxed(),
             )
             .named("filler"),
@@ -300,24 +305,24 @@ impl Pane {
         tab_hovered: bool,
         is_dirty: bool,
         has_conflict: bool,
-        theme: &UiTheme,
+        theme: &theme::Ui,
         cx: &AppContext,
     ) -> ElementBox {
         enum TabCloseButton {}
 
-        let mut clicked_color = theme.tab_icon_dirty;
+        let mut clicked_color = theme.tab.icon_dirty;
         clicked_color.a = 180;
 
         let current_color = if has_conflict {
-            Some(theme.tab_icon_conflict)
+            Some(theme.tab.icon_conflict)
         } else if is_dirty {
-            Some(theme.tab_icon_dirty)
+            Some(theme.tab.icon_dirty)
         } else {
             None
         };
 
         let icon = if tab_hovered {
-            let close_color = current_color.unwrap_or(theme.tab_icon_close);
+            let close_color = current_color.unwrap_or(theme.tab.icon_close);
             let icon = Svg::new("icons/x.svg").with_color(close_color);
 
             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state| {
@@ -326,7 +331,7 @@ impl Pane {
                         .with_background_color(if mouse_state.clicked {
                             clicked_color
                         } else {
-                            theme.tab_icon_dirty
+                            theme.tab.icon_dirty
                         })
                         .with_corner_radius(close_icon_size / 2.)
                         .boxed()