diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index 9a47360691..6b6c3358f2 100644 --- a/gpui/src/elements/label.rs +++ b/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, } @@ -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)] } } } diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml new file mode 100644 index 0000000000..c386289d07 --- /dev/null +++ b/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 diff --git a/zed/assets/themes/light.toml b/zed/assets/themes/light.toml deleted file mode 100644 index 1d706fd724..0000000000 --- a/zed/assets/themes/light.toml +++ /dev/null @@ -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 diff --git a/zed/languages/rust/highlights.scm b/zed/languages/rust/highlights.scm index 2b425faf5c..3276182cd6 100644 --- a/zed/languages/rust/highlights.scm +++ b/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" diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 52371e072a..d5ca0bf1f3 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -2353,6 +2353,7 @@ impl Snapshot { viewport_height: f32, font_cache: &FontCache, layout_cache: &TextLayoutCache, + theme: &Theme, ) -> Result>> { 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); diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index b5b0463c8b..8dd75f867a 100644 --- a/zed/src/editor/element.rs +++ b/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); diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index fb5e4da20d..d7f0f8fff1 100644 --- a/zed/src/file_finder.rs +++ b/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 { + 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()); diff --git a/zed/src/settings.rs b/zed/src/settings.rs index 0813b09a04..85f31873e3 100644 --- a/zed/src/settings.rs +++ b/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, +} + +#[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, + ui: UiTheme, #[serde(default)] - ui: HashMap, + editor: EditorTheme, + #[serde(default)] + syntax: HashMap, } #[derive(Deserialize)] #[serde(untagged)] enum StyleToml { - Color(u32), + Color(Color), Full { - color: Option, + color: Option, weight: Option, #[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(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let rgba_value = u32::deserialize(deserializer)?; + Ok(Self(ColorU::from_u32((rgba_value << 8) + 0xFF))) + } +} + +impl Into 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 for Color { + fn eq(&self, other: &ColorU) -> bool { + self.0.eq(other) + } +} + pub fn channel( font_cache: &FontCache, ) -> Result<(watch::Sender, watch::Receiver)> { 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) -> Result { 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)), diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index aa61cb6842..55a3bfa931 100644 --- a/zed/src/workspace.rs +++ b/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") } diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 60cc6bbf4d..2b152a6996 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; +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::(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::(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., });