From 81041d7841735feacca98799f271d084e62b385b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 3 Aug 2021 13:36:58 -0700 Subject: [PATCH] Restructure Theme with new style objects --- 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(-) create mode 100644 zed/src/theme.rs diff --git a/gpui/src/elements/container.rs b/gpui/src/elements/container.rs index 212e349314..ae13b5d821 100644 --- a/gpui/src/elements/container.rs +++ b/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, - border: Border, - corner_radius: f32, - shadow: Option, + #[serde(default)] + pub margin: Margin, + #[serde(default)] + pub padding: Padding, + #[serde(rename = "background")] + pub background_color: Option, + #[serde(default)] + pub border: Border, + #[serde(default)] + pub corner_radius: f32, + #[serde(default)] + pub shadow: Option, } 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, } diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index ae1c4d17fd..ba9b9e8c5e 100644 --- a/gpui/src/elements/label.rs +++ b/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, - #[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, diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs index b6d89cf3ae..8c83c5268b 100644 --- a/gpui/src/scene.rs +++ b/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, + #[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, diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index fd86c98e67..fb73a3830d 100644 --- a/zed/assets/themes/_base.toml +++ b/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 }, diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 36ff7c8312..3bd1e54c82 100644 --- a/zed/src/file_finder.rs +++ b/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| { diff --git a/zed/src/lib.rs b/zed/src/lib.rs index a02b00dc6a..0236fdd033 100644 --- a/zed/src/lib.rs +++ b/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; diff --git a/zed/src/settings.rs b/zed/src/settings.rs index fcdf230130..42b4afa3d3 100644 --- a/zed/src/settings.rs +++ b/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>>, } -#[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, } -#[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, -} - -#[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::>(), diff --git a/zed/src/theme.rs b/zed/src/theme.rs new file mode 100644 index 0000000000..1a733ff3a6 --- /dev/null +++ b/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, +} + +#[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()], + } + } +} diff --git a/zed/src/theme_selector.rs b/zed/src/theme_selector.rs index c88f1e7005..ec50e92a6e 100644 --- a/zed/src/theme_selector.rs +++ b/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) diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index a8eda55d9d..69c1a3af0b 100644 --- a/zed/src/workspace/pane.rs +++ b/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::(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::(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()