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

This commit is contained in:
Max Brunsfeld 2021-07-29 17:25:35 -07:00
parent c306ac007c
commit 372d2ccb6d
10 changed files with 275 additions and 130 deletions

View file

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

View file

@ -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

View file

@ -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

View file

@ -15,7 +15,27 @@
(function_item name: (identifier) @function.definition) (function_item name: (identifier) @function.definition)
(function_signature_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" "async"
"break" "break"
"const" "const"

View file

@ -2353,6 +2353,7 @@ impl Snapshot {
viewport_height: f32, viewport_height: f32,
font_cache: &FontCache, font_cache: &FontCache,
layout_cache: &TextLayoutCache, layout_cache: &TextLayoutCache,
theme: &Theme,
) -> Result<Vec<Option<text_layout::Line>>> { ) -> Result<Vec<Option<text_layout::Line>>> {
let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; 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( layouts.push(Some(layout_cache.layout_str(
&line_number, &line_number,
self.font_size, 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 buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
let settings = settings::channel(&font_cache).unwrap().1; 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| { let layouts = editor.update(cx, |editor, cx| {
editor editor
.snapshot(cx) .snapshot(cx)
.layout_line_numbers(1000.0, &font_cache, &layout_cache) .layout_line_numbers(1000.0, &font_cache, &layout_cache, &settings.borrow().theme)
.unwrap() .unwrap()
}); });
assert_eq!(layouts.len(), 6); assert_eq!(layouts.len(), 6);

View file

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

View file

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

View file

@ -7,7 +7,12 @@ use gpui::{
}; };
use postage::watch; use postage::watch;
use serde::Deserialize; 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); const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
@ -23,12 +28,50 @@ pub struct Settings {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Theme { pub struct Theme {
pub background_color: ColorU, pub ui: UiTheme,
pub line_number_color: ColorU, pub editor: EditorTheme,
pub default_text_color: ColorU, syntax: Vec<(String, ColorU, FontProperties)>,
syntax_styles: 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)] #[derive(Clone, Debug)]
pub struct ThemeMap(Arc<[StyleId]>); pub struct ThemeMap(Arc<[StyleId]>);
@ -44,7 +87,7 @@ impl Settings {
ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?, ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?,
ui_font_size: 12.0, ui_font_size: 12.0,
theme: Arc::new( 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"), .expect("Failed to parse built-in theme"),
), ),
}) })
@ -61,17 +104,19 @@ impl Theme {
#[derive(Deserialize)] #[derive(Deserialize)]
struct ThemeToml { struct ThemeToml {
#[serde(default)] #[serde(default)]
syntax: HashMap<String, StyleToml>, ui: UiTheme,
#[serde(default)] #[serde(default)]
ui: HashMap<String, u32>, editor: EditorTheme,
#[serde(default)]
syntax: HashMap<String, StyleToml>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
enum StyleToml { enum StyleToml {
Color(u32), Color(Color),
Full { Full {
color: Option<u32>, color: Option<Color>,
weight: Option<toml::Value>, weight: Option<toml::Value>,
#[serde(default)] #[serde(default)]
italic: bool, italic: bool,
@ -81,7 +126,7 @@ impl Theme {
let theme_toml: ThemeToml = let theme_toml: ThemeToml =
toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?; 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 { for (key, style) in theme_toml.syntax {
let (color, weight, italic) = match style { let (color, weight, italic) = match style {
StyleToml::Color(color) => (color, None, false), StyleToml::Color(color) => (color, None, false),
@ -89,55 +134,37 @@ impl Theme {
color, color,
weight, weight,
italic, 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) => { Ok(i) | Err(i) => {
let mut properties = FontProperties::new(); let mut properties = FontProperties::new();
properties.weight = deserialize_weight(weight)?; properties.weight = deserialize_weight(weight)?;
if italic { if italic {
properties.style = FontStyle::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 { Ok(Theme {
background_color, ui: theme_toml.ui,
line_number_color, editor: theme_toml.editor,
default_text_color, syntax,
syntax_styles,
}) })
} }
pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) { pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) {
self.syntax_styles self.syntax.get(id.0 as usize).map_or(
.get(id.0 as usize) (self.editor.default_text.0, FontProperties::new()),
.map_or((self.default_text_color, FontProperties::new()), |entry| { |entry| (entry.1, entry.2),
(entry.1, entry.2) )
})
} }
#[cfg(test)] #[cfg(test)]
pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> { 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() .iter()
.map(|capture_name| { .map(|capture_name| {
theme theme
.syntax_styles .syntax
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, (key, _, _))| { .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( pub fn channel(
font_cache: &FontCache, font_cache: &FontCache,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> { ) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
Ok(watch::channel_with(Settings::new(font_cache)?)) 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> { fn deserialize_weight(weight: Option<toml::Value>) -> Result<FontWeight> {
match &weight { match &weight {
None => return Ok(FontWeight::NORMAL), None => return Ok(FontWeight::NORMAL),
@ -228,8 +292,11 @@ mod tests {
let theme = Theme::parse( let theme = Theme::parse(
r#" r#"
[ui] [ui]
tab_background_active = 0x100000
[editor]
background = 0x00ed00 background = 0x00ed00
line_numbers = 0xdddddd line_number = 0xdddddd
[syntax] [syntax]
"beta.two" = 0xAABBCC "beta.two" = 0xAABBCC
@ -239,24 +306,25 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(theme.background_color, ColorU::from_u32(0x00ED00FF)); assert_eq!(theme.ui.tab_background_active, ColorU::from_u32(0x100000ff));
assert_eq!(theme.line_number_color, ColorU::from_u32(0xddddddff)); assert_eq!(theme.editor.background, ColorU::from_u32(0x00ed00ff));
assert_eq!(theme.editor.line_number, ColorU::from_u32(0xddddddff));
assert_eq!( assert_eq!(
theme.syntax_styles, theme.syntax,
&[ &[
( (
"alpha.one".to_string(), "alpha.one".to_string(),
ColorU::from_u32(0x112233FF), ColorU::from_u32(0x112233ff),
*FontProperties::new().weight(FontWeight::BOLD) *FontProperties::new().weight(FontWeight::BOLD)
), ),
( (
"beta.two".to_string(), "beta.two".to_string(),
ColorU::from_u32(0xAABBCCFF), ColorU::from_u32(0xaabbccff),
*FontProperties::new().weight(FontWeight::NORMAL) *FontProperties::new().weight(FontWeight::NORMAL)
), ),
( (
"gamma.three".to_string(), "gamma.three".to_string(),
ColorU::from_u32(0x000000FF), ColorU::from_u32(0x00000000),
*FontProperties::new() *FontProperties::new()
.weight(FontWeight::LIGHT) .weight(FontWeight::LIGHT)
.style(FontStyle::Italic), .style(FontStyle::Italic),
@ -273,10 +341,9 @@ mod tests {
#[test] #[test]
fn test_theme_map() { fn test_theme_map() {
let theme = Theme { let theme = Theme {
default_text_color: Default::default(), ui: Default::default(),
background_color: ColorU::default(), editor: Default::default(),
line_number_color: ColorU::default(), syntax: [
syntax_styles: [
("function", ColorU::from_u32(0x100000ff)), ("function", ColorU::from_u32(0x100000ff)),
("function.method", ColorU::from_u32(0x200000ff)), ("function.method", ColorU::from_u32(0x200000ff)),
("function.async", ColorU::from_u32(0x300000ff)), ("function.async", ColorU::from_u32(0x300000ff)),

View file

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

View file

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