logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled

Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 02:22:17 +03:30
commit b72a46db68
3984 changed files with 1583326 additions and 0 deletions

36
crates/theme/Cargo.toml Normal file
View File

@@ -0,0 +1,36 @@
[package]
name = "theme"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[features]
default = []
test-support = ["gpui/test-support", "syntax_theme/test-support"]
[lib]
path = "src/theme.rs"
doctest = false
[dependencies]
anyhow.workspace = true
collections.workspace = true
gpui.workspace = true
syntax_theme.workspace = true
palette = { workspace = true, default-features = false, features = ["std"] }
parking_lot.workspace = true
refineable.workspace = true
schemars = { workspace = true, features = ["indexmap2"] }
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
strum.workspace = true
thiserror.workspace = true
uuid.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }

1
crates/theme/LICENSE-GPL Symbolic link
View File

@@ -0,0 +1 @@
../../LICENSE-GPL

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
use std::sync::Arc;
use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance, hsla};
use crate::{
AccentColors, Appearance, DEFAULT_DARK_THEME, PlayerColors, StatusColors,
StatusColorsRefinement, SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeColorsRefinement,
ThemeFamily, ThemeStyles, default_color_scales,
};
/// The default theme family for Zed.
///
/// This is used to construct the default theme fallback values, as well as to
/// have a theme available at compile time for tests.
pub fn zed_default_themes() -> ThemeFamily {
ThemeFamily {
id: "zed-default".to_string(),
name: "Zed Default".into(),
author: "".into(),
themes: vec![zed_default_dark()],
scales: default_color_scales(),
}
}
// If a theme customizes a foreground version of a status color, but does not
// customize the background color, then use a partly-transparent version of the
// foreground color for the background color.
/// Applies default status color backgrounds from their foreground counterparts.
pub fn apply_status_color_defaults(status: &mut StatusColorsRefinement) {
for (fg_color, bg_color) in [
(&status.deleted, &mut status.deleted_background),
(&status.created, &mut status.created_background),
(&status.modified, &mut status.modified_background),
(&status.conflict, &mut status.conflict_background),
(&status.error, &mut status.error_background),
(&status.hidden, &mut status.hidden_background),
] {
if bg_color.is_none()
&& let Some(fg_color) = fg_color
{
*bg_color = Some(fg_color.opacity(0.25));
}
}
}
/// Applies default theme color values derived from player colors.
pub fn apply_theme_color_defaults(
theme_colors: &mut ThemeColorsRefinement,
player_colors: &PlayerColors,
) {
if theme_colors.element_selection_background.is_none() {
let mut selection = player_colors.local().selection;
if selection.a == 1.0 {
selection.a = 0.25;
}
theme_colors.element_selection_background = Some(selection);
}
}
pub(crate) fn zed_default_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
let hover = hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0);
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
let green = hsla(95. / 360., 38. / 100., 62. / 100., 1.0);
let orange = hsla(29. / 360., 54. / 100., 61. / 100., 1.0);
let purple = hsla(286. / 360., 51. / 100., 64. / 100., 1.0);
let red = hsla(355. / 360., 65. / 100., 65. / 100., 1.0);
let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
const ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 1.0,
};
const WORD_ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 0.35,
};
const MODIFIED_COLOR: Hsla = Hsla {
h: 48. / 360.,
s: 0.76,
l: 0.47,
a: 1.0,
};
const REMOVED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 1.0,
};
const WORD_DELETED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 0.80,
};
let player = PlayerColors::dark();
Theme {
id: "one_dark".to_string(),
name: DEFAULT_DARK_THEME.into(),
appearance: Appearance::Dark,
styles: ThemeStyles {
window_background_appearance: WindowBackgroundAppearance::Opaque,
system: SystemColors::default(),
accents: AccentColors(Arc::from(vec![
blue, orange, purple, teal, red, green, yellow,
])),
colors: ThemeColors {
border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
border_focused: hsla(223. / 360., 78. / 100., 65. / 100., 1.),
border_selected: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
border_transparent: SystemColors::default().transparent,
border_disabled: hsla(222.0 / 360., 11.6 / 100., 33.7 / 100., 1.0),
elevated_surface_background: elevated_surface,
surface_background: bg,
background: bg,
element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
element_hover: hover,
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
element_disabled: SystemColors::default().transparent,
element_selection_background: player.local().selection.alpha(0.25),
drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
drop_target_border: hsla(221. / 360., 11. / 100., 86. / 100., 1.0),
ghost_element_background: SystemColors::default().transparent,
ghost_element_hover: hover,
ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
ghost_element_disabled: SystemColors::default().transparent,
text: hsla(221. / 360., 11. / 100., 86. / 100., 1.0),
text_muted: hsla(218.0 / 360., 7. / 100., 46. / 100., 1.0),
text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_disabled: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_accent: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
icon: hsla(222.9 / 360., 9.9 / 100., 86.1 / 100., 1.0),
icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0),
icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_accent: blue,
debugger_accent: red,
status_bar_background: bg,
title_bar_background: bg,
title_bar_inactive_background: bg,
toolbar_background: editor,
tab_bar_background: bg,
tab_inactive_background: bg,
tab_active_background: editor,
search_match_background: bg,
search_active_match_background: bg,
editor_background: editor,
editor_gutter_background: editor,
editor_subheader_background: bg,
editor_active_line_background: hsla(222.9 / 360., 13.5 / 100., 20.4 / 100., 1.0),
editor_highlighted_line_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.1),
editor_debugger_active_line_background: hsla(
207.8 / 360.,
81. / 100.,
66. / 100.,
0.2,
),
editor_line_number: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_active_line_number: hsla(216.0 / 360., 5.9 / 100., 49.6 / 100., 1.0),
editor_hover_line_number: hsla(216.0 / 360., 5.9 / 100., 56.7 / 100., 1.0),
editor_invisible: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_wrap_guide: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
editor_active_wrap_guide: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
editor_indent_guide: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
editor_indent_guide_active: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
editor_document_highlight_read_background: hsla(
207.8 / 360.,
81. / 100.,
66. / 100.,
0.2,
),
editor_document_highlight_write_background: gpui::red(),
editor_document_highlight_bracket_background: gpui::green(),
editor_diff_hunk_added_background: ADDED_COLOR.opacity(0.12),
editor_diff_hunk_added_hollow_background: ADDED_COLOR.opacity(0.06),
editor_diff_hunk_added_hollow_border: ADDED_COLOR.opacity(0.36),
editor_diff_hunk_deleted_background: REMOVED_COLOR.opacity(0.12),
editor_diff_hunk_deleted_hollow_background: REMOVED_COLOR.opacity(0.06),
editor_diff_hunk_deleted_hollow_border: REMOVED_COLOR.opacity(0.36),
terminal_background: bg,
// todo("Use one colors for terminal")
terminal_ansi_background: crate::black().dark().step_12(),
terminal_foreground: crate::white().dark().step_12(),
terminal_bright_foreground: crate::white().dark().step_11(),
terminal_dim_foreground: crate::white().dark().step_10(),
terminal_ansi_black: crate::black().dark().step_12(),
terminal_ansi_red: crate::red().dark().step_11(),
terminal_ansi_green: crate::green().dark().step_11(),
terminal_ansi_yellow: crate::yellow().dark().step_11(),
terminal_ansi_blue: crate::blue().dark().step_11(),
terminal_ansi_magenta: crate::violet().dark().step_11(),
terminal_ansi_cyan: crate::cyan().dark().step_11(),
terminal_ansi_white: crate::neutral().dark().step_12(),
terminal_ansi_bright_black: crate::black().dark().step_11(),
terminal_ansi_bright_red: crate::red().dark().step_10(),
terminal_ansi_bright_green: crate::green().dark().step_10(),
terminal_ansi_bright_yellow: crate::yellow().dark().step_10(),
terminal_ansi_bright_blue: crate::blue().dark().step_10(),
terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
terminal_ansi_bright_white: crate::neutral().dark().step_11(),
terminal_ansi_dim_black: crate::black().dark().step_10(),
terminal_ansi_dim_red: crate::red().dark().step_9(),
terminal_ansi_dim_green: crate::green().dark().step_9(),
terminal_ansi_dim_yellow: crate::yellow().dark().step_9(),
terminal_ansi_dim_blue: crate::blue().dark().step_9(),
terminal_ansi_dim_magenta: crate::violet().dark().step_9(),
terminal_ansi_dim_cyan: crate::cyan().dark().step_9(),
terminal_ansi_dim_white: crate::neutral().dark().step_10(),
panel_background: bg,
panel_focused_border: blue,
panel_indent_guide: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
panel_indent_guide_hover: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
panel_indent_guide_active: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
panel_overlay_background: bg,
panel_overlay_hover: hover,
pane_focused_border: blue,
pane_group_border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
scrollbar_thumb_background: gpui::transparent_black(),
scrollbar_thumb_hover_background: hover,
scrollbar_thumb_active_background: hsla(
225.0 / 360.,
11.8 / 100.,
26.7 / 100.,
1.0,
),
scrollbar_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
scrollbar_track_background: gpui::transparent_black(),
scrollbar_track_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
minimap_thumb_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7),
minimap_thumb_hover_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7),
minimap_thumb_active_background: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 0.7),
minimap_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
editor_foreground: hsla(218. / 360., 14. / 100., 71. / 100., 1.),
link_text_hover: blue,
version_control_added: ADDED_COLOR,
version_control_deleted: REMOVED_COLOR,
version_control_modified: MODIFIED_COLOR,
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: crate::orange().light().step_12(),
version_control_ignored: crate::gray().light().step_12(),
version_control_word_added: WORD_ADDED_COLOR,
version_control_word_deleted: WORD_DELETED_COLOR,
version_control_conflict_marker_ours: crate::green().light().step_12().alpha(0.5),
version_control_conflict_marker_theirs: crate::blue().light().step_12().alpha(0.5),
vim_normal_background: SystemColors::default().transparent,
vim_insert_background: SystemColors::default().transparent,
vim_replace_background: SystemColors::default().transparent,
vim_visual_background: SystemColors::default().transparent,
vim_visual_line_background: SystemColors::default().transparent,
vim_visual_block_background: SystemColors::default().transparent,
vim_yank_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.2),
vim_helix_jump_label_foreground: red,
vim_helix_normal_background: SystemColors::default().transparent,
vim_helix_select_background: SystemColors::default().transparent,
vim_normal_foreground: SystemColors::default().transparent,
vim_insert_foreground: SystemColors::default().transparent,
vim_replace_foreground: SystemColors::default().transparent,
vim_visual_foreground: SystemColors::default().transparent,
vim_visual_line_foreground: SystemColors::default().transparent,
vim_visual_block_foreground: SystemColors::default().transparent,
vim_helix_normal_foreground: SystemColors::default().transparent,
vim_helix_select_foreground: SystemColors::default().transparent,
},
status: StatusColors {
conflict: yellow,
conflict_background: yellow,
conflict_border: yellow,
created: green,
created_background: green,
created_border: green,
deleted: red,
deleted_background: red,
deleted_border: red,
error: red,
error_background: red,
error_border: red,
hidden: gray,
hidden_background: gray,
hidden_border: gray,
hint: blue,
hint_background: blue,
hint_border: blue,
ignored: gray,
ignored_background: gray,
ignored_border: gray,
info: blue,
info_background: blue,
info_border: blue,
modified: yellow,
modified_background: yellow,
modified_border: yellow,
predictive: gray,
predictive_background: gray,
predictive_border: gray,
renamed: blue,
renamed_background: blue,
renamed_border: blue,
success: green,
success_background: green,
success_border: green,
unreachable: gray,
unreachable_background: gray,
unreachable_border: gray,
warning: yellow,
warning_background: yellow,
warning_border: yellow,
},
player,
syntax: Arc::new(SyntaxTheme::new(vec![
("attribute".into(), purple.into()),
("boolean".into(), orange.into()),
("comment".into(), gray.into()),
("comment.doc".into(), gray.into()),
("constant".into(), yellow.into()),
("constructor".into(), blue.into()),
("embedded".into(), HighlightStyle::default()),
(
"emphasis".into(),
HighlightStyle {
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
(
"emphasis.strong".into(),
HighlightStyle {
font_weight: Some(FontWeight::BOLD),
..HighlightStyle::default()
},
),
("enum".into(), teal.into()),
("function".into(), blue.into()),
("function.method".into(), blue.into()),
("function.definition".into(), blue.into()),
("hint".into(), blue.into()),
("keyword".into(), purple.into()),
("label".into(), HighlightStyle::default()),
("link_text".into(), blue.into()),
(
"link_uri".into(),
HighlightStyle {
color: Some(teal),
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
("number".into(), orange.into()),
("operator".into(), HighlightStyle::default()),
("predictive".into(), HighlightStyle::default()),
("preproc".into(), purple.into()),
("primary".into(), HighlightStyle::default()),
("property".into(), red.into()),
("punctuation".into(), HighlightStyle::default()),
("punctuation.bracket".into(), HighlightStyle::default()),
("punctuation.delimiter".into(), HighlightStyle::default()),
("punctuation.list_marker".into(), HighlightStyle::default()),
("punctuation.special".into(), HighlightStyle::default()),
("string".into(), green.into()),
("string.escape".into(), HighlightStyle::default()),
("string.regex".into(), red.into()),
("string.special".into(), HighlightStyle::default()),
("string.special.symbol".into(), HighlightStyle::default()),
("tag".into(), HighlightStyle::default()),
("text.literal".into(), HighlightStyle::default()),
("title".into(), HighlightStyle::default()),
("type".into(), teal.into()),
("variable".into(), HighlightStyle::default()),
("variable.special".into(), red.into()),
("variant".into(), HighlightStyle::default()),
("diff.plus".into(), green.into()),
("diff.minus".into(), red.into()),
])),
},
}
}

View File

@@ -0,0 +1,93 @@
use std::sync::Arc;
use std::time::Instant;
use gpui::{App, Global, ReadGlobal, SharedString};
use parking_lot::RwLock;
#[derive(Default)]
struct FontFamilyCacheState {
loaded_at: Option<Instant>,
font_families: Vec<SharedString>,
}
/// A cache for the list of font families.
///
/// Listing the available font families from the text system is expensive,
/// so we do it once and then use the cached values each render.
#[derive(Default)]
pub struct FontFamilyCache {
state: Arc<RwLock<FontFamilyCacheState>>,
}
#[derive(Default)]
struct GlobalFontFamilyCache(Arc<FontFamilyCache>);
impl Global for GlobalFontFamilyCache {}
impl FontFamilyCache {
/// Initializes the global font family cache.
pub fn init_global(cx: &mut App) {
cx.default_global::<GlobalFontFamilyCache>();
}
/// Returns the global font family cache.
pub fn global(cx: &App) -> Arc<Self> {
GlobalFontFamilyCache::global(cx).0.clone()
}
/// Returns the list of font families.
pub fn list_font_families(&self, cx: &App) -> Vec<SharedString> {
if self.state.read().loaded_at.is_some() {
return self.state.read().font_families.clone();
}
let mut lock = self.state.write();
lock.font_families = cx
.text_system()
.all_font_names()
.into_iter()
.map(SharedString::from)
.collect();
lock.loaded_at = Some(Instant::now());
lock.font_families.clone()
}
/// Returns the list of font families if they have been loaded
pub fn try_list_font_families(&self) -> Option<Vec<SharedString>> {
self.state
.try_read()
.filter(|state| state.loaded_at.is_some())
.map(|state| state.font_families.clone())
}
/// Prefetch all font names in the background
pub async fn prefetch(&self, cx: &gpui::AsyncApp) {
if self
.state
.try_read()
.is_none_or(|state| state.loaded_at.is_some())
{
return;
}
let text_system = cx.update(|cx| App::text_system(cx).clone());
let state = self.state.clone();
cx.background_executor()
.spawn(async move {
// We take this lock in the background executor to ensure that synchronous calls to `list_font_families` are blocked while we are prefetching,
// while not blocking the main thread and risking deadlocks
let mut lock = state.write();
let all_font_names = text_system
.all_font_names()
.into_iter()
.map(SharedString::from)
.collect();
lock.font_families = all_font_names;
lock.loaded_at = Some(Instant::now());
})
.await;
}
}

View File

@@ -0,0 +1,453 @@
use std::sync::{Arc, LazyLock};
use collections::HashMap;
use gpui::SharedString;
use crate::Appearance;
/// A family of icon themes.
pub struct IconThemeFamily {
/// The unique ID for the icon theme family.
pub id: String,
/// The name of the icon theme family.
pub name: SharedString,
/// The author of the icon theme family.
pub author: SharedString,
/// The list of icon themes in the family.
pub themes: Vec<IconTheme>,
}
/// An icon theme.
#[derive(Debug, PartialEq)]
pub struct IconTheme {
/// The unique ID for the icon theme.
pub id: String,
/// The name of the icon theme.
pub name: SharedString,
/// The appearance of the icon theme (e.g., light or dark).
pub appearance: Appearance,
/// The icons used for directories.
pub directory_icons: DirectoryIcons,
/// The icons used for named directories.
pub named_directory_icons: HashMap<String, DirectoryIcons>,
/// The icons used for chevrons.
pub chevron_icons: ChevronIcons,
/// The mapping of file stems to their associated icon keys.
pub file_stems: HashMap<String, String>,
/// The mapping of file suffixes to their associated icon keys.
pub file_suffixes: HashMap<String, String>,
/// The mapping of icon keys to icon definitions.
pub file_icons: HashMap<String, IconDefinition>,
}
/// The icons used for directories.
#[derive(Debug, PartialEq, Clone)]
pub struct DirectoryIcons {
/// The path to the icon to use for a collapsed directory.
pub collapsed: Option<SharedString>,
/// The path to the icon to use for an expanded directory.
pub expanded: Option<SharedString>,
}
/// The icons used for chevrons.
#[derive(Debug, PartialEq)]
pub struct ChevronIcons {
/// The path to the icon to use for a collapsed chevron.
pub collapsed: Option<SharedString>,
/// The path to the icon to use for an expanded chevron.
pub expanded: Option<SharedString>,
}
/// An icon definition.
#[derive(Debug, PartialEq)]
pub struct IconDefinition {
/// The path to the icon file.
pub path: SharedString,
}
const FILE_STEMS_BY_ICON_KEY: &[(&str, &[&str])] = &[
("docker", &["Containerfile", "Dockerfile"]),
("ruby", &["Podfile"]),
("heroku", &["Procfile"]),
];
const FILE_SUFFIXES_BY_ICON_KEY: &[(&str, &[&str])] = &[
("astro", &["astro"]),
(
"audio",
&[
"aac", "flac", "m4a", "mka", "mp3", "ogg", "opus", "wav", "wma", "wv",
],
),
("backup", &["bak"]),
("bicep", &["bicep"]),
("bun", &["lockb"]),
("c", &["c", "h"]),
("cairo", &["cairo"]),
("code", &["handlebars", "metadata", "rkt", "scm"]),
("coffeescript", &["coffee"]),
(
"cpp",
&[
"c++", "h++", "cc", "cpp", "cppm", "cxx", "hh", "hpp", "hxx", "inl", "ixx",
],
),
("crystal", &["cr", "ecr"]),
("csharp", &["cs"]),
("csproj", &["csproj"]),
("css", &["css", "pcss", "postcss"]),
("cue", &["cue"]),
("dart", &["dart"]),
("diff", &["diff"]),
(
"docker",
&[
"docker-compose.yml",
"docker-compose.yaml",
"compose.yml",
"compose.yaml",
],
),
(
"document",
&[
"doc", "docx", "mdx", "odp", "ods", "odt", "pdf", "ppt", "pptx", "rtf", "txt", "xls",
"xlsx",
],
),
("editorconfig", &["editorconfig"]),
("elixir", &["eex", "ex", "exs", "heex", "leex", "neex"]),
("elm", &["elm"]),
(
"erlang",
&[
"Emakefile",
"app.src",
"erl",
"escript",
"hrl",
"rebar.config",
"xrl",
"yrl",
],
),
(
"eslint",
&[
"eslint.config.cjs",
"eslint.config.cts",
"eslint.config.js",
"eslint.config.mjs",
"eslint.config.mts",
"eslint.config.ts",
"eslintrc",
"eslintrc.js",
"eslintrc.json",
],
),
("font", &["otf", "ttf", "woff", "woff2"]),
("fsharp", &["fs"]),
("fsproj", &["fsproj"]),
("gitlab", &["gitlab-ci.yml", "gitlab-ci.yaml"]),
("gleam", &["gleam"]),
("go", &["go", "mod", "work"]),
("graphql", &["gql", "graphql", "graphqls"]),
("haskell", &["hs"]),
("hcl", &["hcl"]),
(
"helm",
&[
"helmfile.yaml",
"helmfile.yml",
"Chart.yaml",
"Chart.yml",
"Chart.lock",
"values.yaml",
"values.yml",
"requirements.yaml",
"requirements.yml",
"tpl",
],
),
("html", &["htm", "html"]),
(
"image",
&[
"avif", "bmp", "gif", "heic", "heif", "ico", "j2k", "jfif", "jp2", "jpeg", "jpg",
"jxl", "png", "psd", "qoi", "svg", "tiff", "webp",
],
),
("ipynb", &["ipynb"]),
("java", &["java"]),
("javascript", &["cjs", "js", "mjs"]),
("json", &["json", "jsonc"]),
("julia", &["jl"]),
("kdl", &["kdl"]),
("kotlin", &["kt"]),
("lock", &["lock"]),
("log", &["log"]),
("lua", &["lua"]),
("luau", &["luau"]),
("markdown", &["markdown", "md"]),
("metal", &["metal"]),
("nim", &["nim", "nims", "nimble"]),
("nix", &["nix"]),
("ocaml", &["ml", "mli", "mlx"]),
("odin", &["odin"]),
("php", &["php"]),
(
"prettier",
&[
"prettier.config.cjs",
"prettier.config.js",
"prettier.config.mjs",
"prettierignore",
"prettierrc",
"prettierrc.cjs",
"prettierrc.js",
"prettierrc.json",
"prettierrc.json5",
"prettierrc.mjs",
"prettierrc.toml",
"prettierrc.yaml",
"prettierrc.yml",
],
),
("prisma", &["prisma"]),
("puppet", &["pp"]),
("python", &["py"]),
("r", &["r", "R"]),
("react", &["cjsx", "ctsx", "jsx", "mjsx", "mtsx", "tsx"]),
("roc", &["roc"]),
("ruby", &["rb"]),
("rust", &["rs"]),
("sass", &["sass", "scss"]),
("scala", &["scala", "sc"]),
("settings", &["conf", "ini"]),
("solidity", &["sol"]),
(
"storage",
&[
"accdb", "csv", "dat", "db", "dbf", "dll", "fmp", "fp7", "frm", "gdb", "ib", "ldf",
"mdb", "mdf", "myd", "myi", "pdb", "RData", "rdata", "sav", "sdf", "sql", "sqlite",
"tsv",
],
),
(
"stylelint",
&[
"stylelint.config.cjs",
"stylelint.config.js",
"stylelint.config.mjs",
"stylelintignore",
"stylelintrc",
"stylelintrc.cjs",
"stylelintrc.js",
"stylelintrc.json",
"stylelintrc.mjs",
"stylelintrc.yaml",
"stylelintrc.yml",
],
),
("surrealql", &["surql"]),
("svelte", &["svelte"]),
("swift", &["swift"]),
("tcl", &["tcl"]),
("template", &["hbs", "plist", "xml"]),
(
"terminal",
&[
"bash",
"bash_aliases",
"bash_login",
"bash_logout",
"bash_profile",
"bashrc",
"fish",
"nu",
"profile",
"ps1",
"sh",
"zlogin",
"zlogout",
"zprofile",
"zsh",
"zsh_aliases",
"zsh_histfile",
"zsh_history",
"zshenv",
"zshrc",
],
),
("terraform", &["tf", "tfvars"]),
("toml", &["toml"]),
("typescript", &["cts", "mts", "ts"]),
("v", &["v", "vsh", "vv"]),
(
"vcs",
&[
"COMMIT_EDITMSG",
"EDIT_DESCRIPTION",
"MERGE_MSG",
"NOTES_EDITMSG",
"TAG_EDITMSG",
"gitattributes",
"gitignore",
"gitkeep",
"gitmodules",
],
),
("vbproj", &["vbproj"]),
("video", &["avi", "m4v", "mkv", "mov", "mp4", "webm", "wmv"]),
("vs_sln", &["sln"]),
("vs_suo", &["suo"]),
("vue", &["vue"]),
("vyper", &["vy", "vyi"]),
("wgsl", &["wgsl"]),
("yaml", &["yaml", "yml"]),
("zig", &["zig"]),
];
/// A mapping of a file type identifier to its corresponding icon.
const FILE_ICONS: &[(&str, &str)] = &[
("astro", "icons/file_icons/astro.svg"),
("audio", "icons/file_icons/audio.svg"),
("bicep", "icons/file_icons/file.svg"),
("bun", "icons/file_icons/bun.svg"),
("c", "icons/file_icons/c.svg"),
("cairo", "icons/file_icons/cairo.svg"),
("code", "icons/file_icons/code.svg"),
("coffeescript", "icons/file_icons/coffeescript.svg"),
("cpp", "icons/file_icons/cpp.svg"),
("crystal", "icons/file_icons/file.svg"),
("csharp", "icons/file_icons/file.svg"),
("csproj", "icons/file_icons/file.svg"),
("css", "icons/file_icons/css.svg"),
("cue", "icons/file_icons/file.svg"),
("dart", "icons/file_icons/dart.svg"),
("default", "icons/file_icons/file.svg"),
("diff", "icons/file_icons/diff.svg"),
("docker", "icons/file_icons/docker.svg"),
("document", "icons/file_icons/book.svg"),
("editorconfig", "icons/file_icons/editorconfig.svg"),
("elixir", "icons/file_icons/elixir.svg"),
("elm", "icons/file_icons/elm.svg"),
("erlang", "icons/file_icons/erlang.svg"),
("eslint", "icons/file_icons/eslint.svg"),
("font", "icons/file_icons/font.svg"),
("fsharp", "icons/file_icons/fsharp.svg"),
("fsproj", "icons/file_icons/file.svg"),
("gitlab", "icons/file_icons/gitlab.svg"),
("gleam", "icons/file_icons/gleam.svg"),
("go", "icons/file_icons/go.svg"),
("graphql", "icons/file_icons/graphql.svg"),
("haskell", "icons/file_icons/haskell.svg"),
("hcl", "icons/file_icons/hcl.svg"),
("helm", "icons/file_icons/helm.svg"),
("heroku", "icons/file_icons/heroku.svg"),
("html", "icons/file_icons/html.svg"),
("image", "icons/file_icons/image.svg"),
("ipynb", "icons/file_icons/jupyter.svg"),
("java", "icons/file_icons/java.svg"),
("javascript", "icons/file_icons/javascript.svg"),
("json", "icons/file_icons/code.svg"),
("julia", "icons/file_icons/julia.svg"),
("kdl", "icons/file_icons/kdl.svg"),
("kotlin", "icons/file_icons/kotlin.svg"),
("lock", "icons/file_icons/lock.svg"),
("log", "icons/file_icons/info.svg"),
("lua", "icons/file_icons/lua.svg"),
("luau", "icons/file_icons/luau.svg"),
("markdown", "icons/file_icons/book.svg"),
("metal", "icons/file_icons/metal.svg"),
("nim", "icons/file_icons/nim.svg"),
("nix", "icons/file_icons/nix.svg"),
("ocaml", "icons/file_icons/ocaml.svg"),
("odin", "icons/file_icons/odin.svg"),
("phoenix", "icons/file_icons/phoenix.svg"),
("php", "icons/file_icons/php.svg"),
("prettier", "icons/file_icons/prettier.svg"),
("prisma", "icons/file_icons/prisma.svg"),
("puppet", "icons/file_icons/puppet.svg"),
("python", "icons/file_icons/python.svg"),
("r", "icons/file_icons/r.svg"),
("react", "icons/file_icons/react.svg"),
("roc", "icons/file_icons/roc.svg"),
("ruby", "icons/file_icons/ruby.svg"),
("rust", "icons/file_icons/rust.svg"),
("sass", "icons/file_icons/sass.svg"),
("scala", "icons/file_icons/scala.svg"),
("settings", "icons/file_icons/settings.svg"),
("solidity", "icons/file_icons/file.svg"),
("storage", "icons/file_icons/database.svg"),
("stylelint", "icons/file_icons/javascript.svg"),
("surrealql", "icons/file_icons/surrealql.svg"),
("svelte", "icons/file_icons/html.svg"),
("swift", "icons/file_icons/swift.svg"),
("tcl", "icons/file_icons/tcl.svg"),
("template", "icons/file_icons/html.svg"),
("terminal", "icons/file_icons/terminal.svg"),
("terraform", "icons/file_icons/terraform.svg"),
("toml", "icons/file_icons/toml.svg"),
("typescript", "icons/file_icons/typescript.svg"),
("v", "icons/file_icons/v.svg"),
("vbproj", "icons/file_icons/file.svg"),
("vcs", "icons/file_icons/git.svg"),
("video", "icons/file_icons/video.svg"),
("vs_sln", "icons/file_icons/file.svg"),
("vs_suo", "icons/file_icons/file.svg"),
("vue", "icons/file_icons/vue.svg"),
("vyper", "icons/file_icons/vyper.svg"),
("wgsl", "icons/file_icons/wgsl.svg"),
("yaml", "icons/file_icons/yaml.svg"),
("zig", "icons/file_icons/zig.svg"),
];
/// Returns a mapping of file associations to icon keys.
fn icon_keys_by_association(
associations_by_icon_key: &[(&str, &[&str])],
) -> HashMap<String, String> {
let mut icon_keys_by_association = HashMap::default();
for (icon_key, associations) in associations_by_icon_key {
for association in *associations {
icon_keys_by_association.insert(association.to_string(), icon_key.to_string());
}
}
icon_keys_by_association
}
/// The name of the default icon theme.
pub const DEFAULT_ICON_THEME_NAME: &str = "Zed (Default)";
static DEFAULT_ICON_THEME: LazyLock<Arc<IconTheme>> = LazyLock::new(|| {
Arc::new(IconTheme {
id: "zed".into(),
name: DEFAULT_ICON_THEME_NAME.into(),
appearance: Appearance::Dark,
directory_icons: DirectoryIcons {
collapsed: Some("icons/file_icons/folder.svg".into()),
expanded: Some("icons/file_icons/folder_open.svg".into()),
},
named_directory_icons: HashMap::default(),
chevron_icons: ChevronIcons {
collapsed: Some("icons/file_icons/chevron_right.svg".into()),
expanded: Some("icons/file_icons/chevron_down.svg".into()),
},
file_stems: icon_keys_by_association(FILE_STEMS_BY_ICON_KEY),
file_suffixes: icon_keys_by_association(FILE_SUFFIXES_BY_ICON_KEY),
file_icons: HashMap::from_iter(FILE_ICONS.iter().map(|(ty, path)| {
(
ty.to_string(),
IconDefinition {
path: (*path).into(),
},
)
})),
})
});
/// Returns the default icon theme.
pub fn default_icon_theme() -> Arc<IconTheme> {
DEFAULT_ICON_THEME.clone()
}

View File

@@ -0,0 +1,50 @@
#![allow(missing_docs)]
use gpui::SharedString;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::AppearanceContent;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IconThemeFamilyContent {
pub name: String,
pub author: String,
pub themes: Vec<IconThemeContent>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IconThemeContent {
pub name: String,
pub appearance: AppearanceContent,
#[serde(default)]
pub directory_icons: DirectoryIconsContent,
#[serde(default)]
pub named_directory_icons: HashMap<String, DirectoryIconsContent>,
#[serde(default)]
pub chevron_icons: ChevronIconsContent,
#[serde(default)]
pub file_stems: HashMap<String, String>,
#[serde(default)]
pub file_suffixes: HashMap<String, String>,
#[serde(default)]
pub file_icons: HashMap<String, IconDefinitionContent>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct DirectoryIconsContent {
pub collapsed: Option<SharedString>,
pub expanded: Option<SharedString>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ChevronIconsContent {
pub collapsed: Option<SharedString>,
pub expanded: Option<SharedString>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IconDefinitionContent {
pub path: SharedString,
}

View File

@@ -0,0 +1,331 @@
use std::sync::Arc;
use std::{fmt::Debug, path::Path};
use anyhow::Result;
use collections::HashMap;
use gpui::{App, AssetSource, Global, SharedString};
use parking_lot::RwLock;
use thiserror::Error;
use crate::{
Appearance, AppearanceContent, ChevronIcons, DEFAULT_ICON_THEME_NAME, DirectoryIcons,
IconDefinition, IconTheme, IconThemeFamilyContent, Theme, ThemeFamily, default_icon_theme,
};
/// The metadata for a theme.
#[derive(Debug, Clone)]
pub struct ThemeMeta {
/// The name of the theme.
pub name: SharedString,
/// The appearance of the theme.
pub appearance: Appearance,
}
/// An error indicating that the theme with the given name was not found.
#[derive(Debug, Error, Clone)]
#[error("theme not found: {0}")]
pub struct ThemeNotFoundError(pub SharedString);
/// An error indicating that the icon theme with the given name was not found.
#[derive(Debug, Error, Clone)]
#[error("icon theme not found: {0}")]
pub struct IconThemeNotFoundError(pub SharedString);
/// The global [`ThemeRegistry`].
///
/// This newtype exists for obtaining a unique [`TypeId`](std::any::TypeId) when
/// inserting the [`ThemeRegistry`] into the context as a global.
///
/// This should not be exposed outside of this module.
#[derive(Default)]
struct GlobalThemeRegistry(Arc<ThemeRegistry>);
impl std::ops::DerefMut for GlobalThemeRegistry {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::ops::Deref for GlobalThemeRegistry {
type Target = Arc<ThemeRegistry>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Global for GlobalThemeRegistry {}
struct ThemeRegistryState {
themes: HashMap<SharedString, Arc<Theme>>,
icon_themes: HashMap<SharedString, Arc<IconTheme>>,
/// Whether the extensions have been loaded yet.
extensions_loaded: bool,
}
/// The registry for themes.
pub struct ThemeRegistry {
state: RwLock<ThemeRegistryState>,
assets: Box<dyn AssetSource>,
}
impl ThemeRegistry {
/// Returns the global [`ThemeRegistry`].
pub fn global(cx: &App) -> Arc<Self> {
cx.global::<GlobalThemeRegistry>().0.clone()
}
/// Returns the global [`ThemeRegistry`].
///
/// Inserts a default [`ThemeRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut App) -> Arc<Self> {
cx.default_global::<GlobalThemeRegistry>().0.clone()
}
/// Returns the global [`ThemeRegistry`] if it exists.
pub fn try_global(cx: &mut App) -> Option<Arc<Self>> {
cx.try_global::<GlobalThemeRegistry>().map(|t| t.0.clone())
}
/// Sets the global [`ThemeRegistry`].
pub(crate) fn set_global(assets: Box<dyn AssetSource>, cx: &mut App) {
cx.set_global(GlobalThemeRegistry(Arc::new(ThemeRegistry::new(assets))));
}
/// Returns the asset source used by this registry.
pub fn assets(&self) -> &dyn AssetSource {
self.assets.as_ref()
}
/// Creates a new [`ThemeRegistry`] with the given [`AssetSource`].
pub fn new(assets: Box<dyn AssetSource>) -> Self {
let registry = Self {
state: RwLock::new(ThemeRegistryState {
themes: HashMap::default(),
icon_themes: HashMap::default(),
extensions_loaded: false,
}),
assets,
};
// We're loading the Zed default theme, as we need a theme to be loaded
// for tests.
registry.insert_theme_families([crate::fallback_themes::zed_default_themes()]);
let default_icon_theme = crate::default_icon_theme();
registry
.state
.write()
.icon_themes
.insert(default_icon_theme.name.clone(), default_icon_theme);
registry
}
/// Returns whether the extensions have been loaded.
pub fn extensions_loaded(&self) -> bool {
self.state.read().extensions_loaded
}
/// Sets the flag indicating that the extensions have loaded.
pub fn set_extensions_loaded(&self) {
self.state.write().extensions_loaded = true;
}
/// Inserts the given theme families into the registry.
pub fn insert_theme_families(&self, families: impl IntoIterator<Item = ThemeFamily>) {
for family in families.into_iter() {
self.insert_themes(family.themes);
}
}
/// Registers theme families for use in tests.
#[cfg(any(test, feature = "test-support"))]
pub fn register_test_themes(&self, families: impl IntoIterator<Item = ThemeFamily>) {
self.insert_theme_families(families);
}
/// Registers icon themes for use in tests.
#[cfg(any(test, feature = "test-support"))]
pub fn register_test_icon_themes(&self, icon_themes: impl IntoIterator<Item = IconTheme>) {
let mut state = self.state.write();
for icon_theme in icon_themes {
state
.icon_themes
.insert(icon_theme.name.clone(), Arc::new(icon_theme));
}
}
/// Inserts the given themes into the registry.
pub fn insert_themes(&self, themes: impl IntoIterator<Item = Theme>) {
let mut state = self.state.write();
for theme in themes.into_iter() {
state.themes.insert(theme.name.clone(), Arc::new(theme));
}
}
/// Removes the themes with the given names from the registry.
pub fn remove_user_themes(&self, themes_to_remove: &[SharedString]) {
self.state
.write()
.themes
.retain(|name, _| !themes_to_remove.contains(name))
}
/// Removes all themes from the registry.
pub fn clear(&self) {
self.state.write().themes.clear();
}
/// Returns the names of all themes in the registry.
pub fn list_names(&self) -> Vec<SharedString> {
let mut names = self.state.read().themes.keys().cloned().collect::<Vec<_>>();
names.sort();
names
}
/// Returns the metadata of all themes in the registry.
pub fn list(&self) -> Vec<ThemeMeta> {
self.state
.read()
.themes
.values()
.map(|theme| ThemeMeta {
name: theme.name.clone(),
appearance: theme.appearance(),
})
.collect()
}
/// Returns the theme with the given name.
pub fn get(&self, name: &str) -> Result<Arc<Theme>, ThemeNotFoundError> {
self.state
.read()
.themes
.get(name)
.ok_or_else(|| ThemeNotFoundError(name.to_string().into()))
.cloned()
}
/// Returns the default icon theme.
pub fn default_icon_theme(&self) -> Result<Arc<IconTheme>, IconThemeNotFoundError> {
self.get_icon_theme(DEFAULT_ICON_THEME_NAME)
}
/// Returns the metadata of all icon themes in the registry.
pub fn list_icon_themes(&self) -> Vec<ThemeMeta> {
self.state
.read()
.icon_themes
.values()
.map(|theme| ThemeMeta {
name: theme.name.clone(),
appearance: theme.appearance,
})
.collect()
}
/// Returns the icon theme with the specified name.
pub fn get_icon_theme(&self, name: &str) -> Result<Arc<IconTheme>, IconThemeNotFoundError> {
self.state
.read()
.icon_themes
.get(name)
.ok_or_else(|| IconThemeNotFoundError(name.to_string().into()))
.cloned()
}
/// Removes the icon themes with the given names from the registry.
pub fn remove_icon_themes(&self, icon_themes_to_remove: &[SharedString]) {
self.state
.write()
.icon_themes
.retain(|name, _| !icon_themes_to_remove.contains(name))
}
/// Loads the icon theme from the icon theme family and adds it to the registry.
///
/// The `icons_root_dir` parameter indicates the root directory from which
/// the relative paths to icons in the theme should be resolved against.
pub fn load_icon_theme(
&self,
icon_theme_family: IconThemeFamilyContent,
icons_root_dir: &Path,
) -> Result<()> {
let resolve_icon_path = |path: SharedString| {
icons_root_dir
.join(path.as_ref())
.to_string_lossy()
.to_string()
.into()
};
let default_icon_theme = default_icon_theme();
let mut state = self.state.write();
for icon_theme in icon_theme_family.themes {
let mut file_stems = default_icon_theme.file_stems.clone();
file_stems.extend(icon_theme.file_stems);
let mut file_suffixes = default_icon_theme.file_suffixes.clone();
file_suffixes.extend(icon_theme.file_suffixes);
let mut named_directory_icons = default_icon_theme.named_directory_icons.clone();
named_directory_icons.extend(icon_theme.named_directory_icons.into_iter().map(
|(key, value)| {
(
key,
DirectoryIcons {
collapsed: value.collapsed.map(resolve_icon_path),
expanded: value.expanded.map(resolve_icon_path),
},
)
},
));
let icon_theme = IconTheme {
id: uuid::Uuid::new_v4().to_string(),
name: icon_theme.name.into(),
appearance: match icon_theme.appearance {
AppearanceContent::Light => Appearance::Light,
AppearanceContent::Dark => Appearance::Dark,
},
directory_icons: DirectoryIcons {
collapsed: icon_theme.directory_icons.collapsed.map(resolve_icon_path),
expanded: icon_theme.directory_icons.expanded.map(resolve_icon_path),
},
named_directory_icons,
chevron_icons: ChevronIcons {
collapsed: icon_theme.chevron_icons.collapsed.map(resolve_icon_path),
expanded: icon_theme.chevron_icons.expanded.map(resolve_icon_path),
},
file_stems,
file_suffixes,
file_icons: icon_theme
.file_icons
.into_iter()
.map(|(key, icon)| {
(
key,
IconDefinition {
path: resolve_icon_path(icon.path),
},
)
})
.collect(),
};
state
.icon_themes
.insert(icon_theme.name.clone(), Arc::new(icon_theme));
}
Ok(())
}
}
impl Default for ThemeRegistry {
fn default() -> Self {
Self::new(Box::new(()))
}
}

298
crates/theme/src/scale.rs Normal file
View File

@@ -0,0 +1,298 @@
#![allow(missing_docs)]
use gpui::{App, Hsla, SharedString};
use crate::{ActiveTheme, Appearance};
/// A collection of colors that are used to style the UI.
///
/// Each step has a semantic meaning, and is used to style different parts of the UI.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ColorScaleStep(usize);
impl ColorScaleStep {
pub const ONE: Self = Self(1);
pub const TWO: Self = Self(2);
pub const THREE: Self = Self(3);
pub const FOUR: Self = Self(4);
pub const FIVE: Self = Self(5);
pub const SIX: Self = Self(6);
pub const SEVEN: Self = Self(7);
pub const EIGHT: Self = Self(8);
pub const NINE: Self = Self(9);
pub const TEN: Self = Self(10);
pub const ELEVEN: Self = Self(11);
pub const TWELVE: Self = Self(12);
/// All of the steps in a [`ColorScale`].
pub const ALL: [ColorScaleStep; 12] = [
Self::ONE,
Self::TWO,
Self::THREE,
Self::FOUR,
Self::FIVE,
Self::SIX,
Self::SEVEN,
Self::EIGHT,
Self::NINE,
Self::TEN,
Self::ELEVEN,
Self::TWELVE,
];
}
/// A scale of colors for a given [`ColorScaleSet`].
///
/// Each [`ColorScale`] contains exactly 12 colors. Refer to
/// [`ColorScaleStep`] for a reference of what each step is used for.
pub struct ColorScale(Vec<Hsla>);
impl FromIterator<Hsla> for ColorScale {
fn from_iter<T: IntoIterator<Item = Hsla>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}
impl ColorScale {
/// Returns the specified step in the [`ColorScale`].
#[inline]
pub fn step(&self, step: ColorScaleStep) -> Hsla {
// Steps are one-based, so we need convert to the zero-based vec index.
self.0[step.0 - 1]
}
/// `Step 1` - Used for main application backgrounds.
///
/// This step provides a neutral base for any overlaying components, ideal for applications' main backdrop or empty spaces such as canvas areas.
///
#[inline]
pub fn step_1(&self) -> Hsla {
self.step(ColorScaleStep::ONE)
}
/// `Step 2` - Used for both main application backgrounds and subtle component backgrounds.
///
/// Like `Step 1`, this step allows variations in background styles, from striped tables, sidebar backgrounds, to card backgrounds.
#[inline]
pub fn step_2(&self) -> Hsla {
self.step(ColorScaleStep::TWO)
}
/// `Step 3` - Used for UI component backgrounds in their normal states.
///
/// This step maintains accessibility by guaranteeing a contrast ratio of 4.5:1 with steps 11 and 12 for text. It could also suit hover states for transparent components.
#[inline]
pub fn step_3(&self) -> Hsla {
self.step(ColorScaleStep::THREE)
}
/// `Step 4` - Used for UI component backgrounds in their hover states.
///
/// Also suited for pressed or selected states of components with a transparent background.
#[inline]
pub fn step_4(&self) -> Hsla {
self.step(ColorScaleStep::FOUR)
}
/// `Step 5` - Used for UI component backgrounds in their pressed or selected states.
#[inline]
pub fn step_5(&self) -> Hsla {
self.step(ColorScaleStep::FIVE)
}
/// `Step 6` - Used for subtle borders on non-interactive components.
///
/// Its usage spans from sidebars' borders, headers' dividers, cards' outlines, to alerts' edges and separators.
#[inline]
pub fn step_6(&self) -> Hsla {
self.step(ColorScaleStep::SIX)
}
/// `Step 7` - Used for subtle borders on interactive components.
///
/// This step subtly delineates the boundary of elements users interact with.
#[inline]
pub fn step_7(&self) -> Hsla {
self.step(ColorScaleStep::SEVEN)
}
/// `Step 8` - Used for stronger borders on interactive components and focus rings.
///
/// It strengthens the visibility and accessibility of active elements and their focus states.
#[inline]
pub fn step_8(&self) -> Hsla {
self.step(ColorScaleStep::EIGHT)
}
/// `Step 9` - Used for solid backgrounds.
///
/// `Step 9` is the most saturated step, having the least mix of white or black.
///
/// Due to its high chroma, `Step 9` is versatile and particularly useful for semantic colors such as
/// error, warning, and success indicators.
#[inline]
pub fn step_9(&self) -> Hsla {
self.step(ColorScaleStep::NINE)
}
/// `Step 10` - Used for hovered or active solid backgrounds, particularly when `Step 9` is their normal state.
///
/// May also be used for extremely low contrast text. This should be used sparingly, as it may be difficult to read.
#[inline]
pub fn step_10(&self) -> Hsla {
self.step(ColorScaleStep::TEN)
}
/// `Step 11` - Used for text and icons requiring low contrast or less emphasis.
#[inline]
pub fn step_11(&self) -> Hsla {
self.step(ColorScaleStep::ELEVEN)
}
/// `Step 12` - Used for text and icons requiring high contrast or prominence.
#[inline]
pub fn step_12(&self) -> Hsla {
self.step(ColorScaleStep::TWELVE)
}
}
pub struct ColorScales {
pub gray: ColorScaleSet,
pub mauve: ColorScaleSet,
pub slate: ColorScaleSet,
pub sage: ColorScaleSet,
pub olive: ColorScaleSet,
pub sand: ColorScaleSet,
pub gold: ColorScaleSet,
pub bronze: ColorScaleSet,
pub brown: ColorScaleSet,
pub yellow: ColorScaleSet,
pub amber: ColorScaleSet,
pub orange: ColorScaleSet,
pub tomato: ColorScaleSet,
pub red: ColorScaleSet,
pub ruby: ColorScaleSet,
pub crimson: ColorScaleSet,
pub pink: ColorScaleSet,
pub plum: ColorScaleSet,
pub purple: ColorScaleSet,
pub violet: ColorScaleSet,
pub iris: ColorScaleSet,
pub indigo: ColorScaleSet,
pub blue: ColorScaleSet,
pub cyan: ColorScaleSet,
pub teal: ColorScaleSet,
pub jade: ColorScaleSet,
pub green: ColorScaleSet,
pub grass: ColorScaleSet,
pub lime: ColorScaleSet,
pub mint: ColorScaleSet,
pub sky: ColorScaleSet,
pub black: ColorScaleSet,
pub white: ColorScaleSet,
}
impl IntoIterator for ColorScales {
type Item = ColorScaleSet;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
vec![
self.gray,
self.mauve,
self.slate,
self.sage,
self.olive,
self.sand,
self.gold,
self.bronze,
self.brown,
self.yellow,
self.amber,
self.orange,
self.tomato,
self.red,
self.ruby,
self.crimson,
self.pink,
self.plum,
self.purple,
self.violet,
self.iris,
self.indigo,
self.blue,
self.cyan,
self.teal,
self.jade,
self.green,
self.grass,
self.lime,
self.mint,
self.sky,
self.black,
self.white,
]
.into_iter()
}
}
/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale.
pub struct ColorScaleSet {
name: SharedString,
light: ColorScale,
dark: ColorScale,
light_alpha: ColorScale,
dark_alpha: ColorScale,
}
impl ColorScaleSet {
pub fn new(
name: impl Into<SharedString>,
light: ColorScale,
light_alpha: ColorScale,
dark: ColorScale,
dark_alpha: ColorScale,
) -> Self {
Self {
name: name.into(),
light,
light_alpha,
dark,
dark_alpha,
}
}
pub fn name(&self) -> &SharedString {
&self.name
}
pub fn light(&self) -> &ColorScale {
&self.light
}
pub fn light_alpha(&self) -> &ColorScale {
&self.light_alpha
}
pub fn dark(&self) -> &ColorScale {
&self.dark
}
pub fn dark_alpha(&self) -> &ColorScale {
&self.dark_alpha
}
pub fn step(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light().step(step),
Appearance::Dark => self.dark().step(step),
}
}
pub fn step_alpha(&self, cx: &App, step: ColorScaleStep) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light_alpha.step(step),
Appearance::Dark => self.dark_alpha.step(step),
}
}
}

View File

@@ -0,0 +1,30 @@
#![allow(missing_docs)]
use gpui::Hsla;
use palette::FromColor;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// The appearance of a theme in serialized content.
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AppearanceContent {
Light,
Dark,
}
/// Parses a color string into an [`Hsla`] value.
pub fn try_parse_color(color: &str) -> anyhow::Result<Hsla> {
let rgba = gpui::Rgba::try_from(color)?;
let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
let hsla = palette::Hsla::from_color(rgba);
let hsla = gpui::hsla(
hsla.hue.into_positive_degrees() / 360.,
hsla.saturation,
hsla.lightness,
hsla.alpha,
);
Ok(hsla)
}

View File

@@ -0,0 +1,13 @@
mod accents;
mod colors;
mod players;
mod status;
mod syntax;
mod system;
pub use accents::*;
pub use colors::*;
pub use players::*;
pub use status::*;
pub use syntax::*;
pub use system::*;

View File

@@ -0,0 +1,68 @@
use std::sync::Arc;
use gpui::Hsla;
use serde::Deserialize;
use crate::{
amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, tomato,
};
/// A collection of colors that are used to color indent aware lines in the editor.
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct AccentColors(pub Arc<[Hsla]>);
impl Default for AccentColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// TODO "Find a way to not need this for Refinable"
fn default() -> Self {
Self::dark()
}
}
impl AccentColors {
/// Returns the set of dark accent colors.
pub fn dark() -> Self {
Self(Arc::from(vec![
blue().dark().step_9(),
orange().dark().step_9(),
pink().dark().step_9(),
lime().dark().step_9(),
purple().dark().step_9(),
amber().dark().step_9(),
jade().dark().step_9(),
tomato().dark().step_9(),
cyan().dark().step_9(),
gold().dark().step_9(),
grass().dark().step_9(),
indigo().dark().step_9(),
iris().dark().step_9(),
]))
}
/// Returns the set of light accent colors.
pub fn light() -> Self {
Self(Arc::from(vec![
blue().light().step_9(),
orange().light().step_9(),
pink().light().step_9(),
lime().light().step_9(),
purple().light().step_9(),
amber().light().step_9(),
jade().light().step_9(),
tomato().light().step_9(),
cyan().light().step_9(),
gold().light().step_9(),
grass().light().step_9(),
indigo().light().step_9(),
iris().light().step_9(),
]))
}
}
impl AccentColors {
/// Returns the color for the given index.
pub fn color_for_index(&self, index: u32) -> Hsla {
self.0[index as usize % self.0.len()]
}
}

View File

@@ -0,0 +1,685 @@
#![allow(missing_docs)]
use gpui::{App, Hsla, SharedString, WindowBackgroundAppearance};
use refineable::Refineable;
use std::sync::Arc;
use strum::{AsRefStr, EnumIter, IntoEnumIterator};
use crate::{
AccentColors, ActiveTheme, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme,
SystemColors,
};
#[derive(Refineable, Clone, Debug, PartialEq)]
#[refineable(Debug, serde::Deserialize)]
pub struct ThemeColors {
/// Border color. Used for most borders, is usually a high contrast color.
pub border: Hsla,
/// Border color. Used for deemphasized borders, like a visual divider between two sections
pub border_variant: Hsla,
/// Border color. Used for focused elements, like keyboard focused list item.
pub border_focused: Hsla,
/// Border color. Used for selected elements, like an active search filter or selected checkbox.
pub border_selected: Hsla,
/// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change.
pub border_transparent: Hsla,
/// Border color. Used for disabled elements, like a disabled input or button.
pub border_disabled: Hsla,
/// Border color. Used for elevated surfaces, like a context menu, popup, or dialog.
pub elevated_surface_background: Hsla,
/// Background Color. Used for grounded surfaces like a panel or tab.
pub surface_background: Hsla,
/// Background Color. Used for the app background and blank panels or windows.
pub background: Hsla,
/// Background Color. Used for the background of an element that should have a different background than the surface it's on.
///
/// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
///
/// For an element that should have the same background as the surface it's on, use `ghost_element_background`.
pub element_background: Hsla,
/// Background Color. Used for the hover state of an element that should have a different background than the surface it's on.
///
/// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
pub element_hover: Hsla,
/// Background Color. Used for the active state of an element that should have a different background than the surface it's on.
///
/// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed.
pub element_active: Hsla,
/// Background Color. Used for the selected state of an element that should have a different background than the surface it's on.
///
/// Selected states are triggered by the element being selected (or "activated") by the user.
///
/// This could include a selected checkbox, a toggleable button that is toggled on, etc.
pub element_selected: Hsla,
/// Background Color. Used for the background of selections in a UI element.
pub element_selection_background: Hsla,
/// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on.
///
/// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
pub element_disabled: Hsla,
/// Background Color. Used for the area that shows where a dragged element will be dropped.
pub drop_target_background: Hsla,
/// Border Color. Used for the border that shows where a dragged element will be dropped.
pub drop_target_border: Hsla,
/// Used for the background of a ghost element that should have the same background as the surface it's on.
///
/// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
///
/// For an element that should have a different background than the surface it's on, use `element_background`.
pub ghost_element_background: Hsla,
/// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on.
///
/// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
pub ghost_element_hover: Hsla,
/// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on.
///
/// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed.
pub ghost_element_active: Hsla,
/// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on.
///
/// Selected states are triggered by the element being selected (or "activated") by the user.
///
/// This could include a selected checkbox, a toggleable button that is toggled on, etc.
pub ghost_element_selected: Hsla,
/// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on.
///
/// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
pub ghost_element_disabled: Hsla,
/// Text Color. Default text color used for most text.
pub text: Hsla,
/// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color.
pub text_muted: Hsla,
/// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data.
pub text_placeholder: Hsla,
/// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state.
pub text_disabled: Hsla,
/// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search.
pub text_accent: Hsla,
/// Fill Color. Used for the default fill color of an icon.
pub icon: Hsla,
/// Fill Color. Used for the muted or deemphasized fill color of an icon.
///
/// This might be used to show an icon in an inactive pane, or to deemphasize a series of icons to give them less visual weight.
pub icon_muted: Hsla,
/// Fill Color. Used for the disabled fill color of an icon.
///
/// Disabled states are shown when a user cannot interact with an element, like a icon button.
pub icon_disabled: Hsla,
/// Fill Color. Used for the placeholder fill color of an icon.
///
/// This might be used to show an icon in an input that disappears when the user enters text.
pub icon_placeholder: Hsla,
/// Fill Color. Used for the accent fill color of an icon.
///
/// This might be used to show when a toggleable icon button is selected.
pub icon_accent: Hsla,
/// Color used to accent some debugger elements
/// Is used by breakpoints
pub debugger_accent: Hsla,
// ===
// UI Elements
// ===
pub status_bar_background: Hsla,
pub title_bar_background: Hsla,
pub title_bar_inactive_background: Hsla,
pub toolbar_background: Hsla,
pub tab_bar_background: Hsla,
pub tab_inactive_background: Hsla,
pub tab_active_background: Hsla,
pub search_match_background: Hsla,
pub search_active_match_background: Hsla,
pub panel_background: Hsla,
pub panel_focused_border: Hsla,
pub panel_indent_guide: Hsla,
pub panel_indent_guide_hover: Hsla,
pub panel_indent_guide_active: Hsla,
/// The color of the overlay surface on top of panel.
pub panel_overlay_background: Hsla,
/// The color of the overlay surface on top of panel when hovered over.
pub panel_overlay_hover: Hsla,
pub pane_focused_border: Hsla,
pub pane_group_border: Hsla,
/// The color of the scrollbar thumb.
pub scrollbar_thumb_background: Hsla,
/// The color of the scrollbar thumb when hovered over.
pub scrollbar_thumb_hover_background: Hsla,
/// The color of the scrollbar thumb whilst being actively dragged.
pub scrollbar_thumb_active_background: Hsla,
/// The border color of the scrollbar thumb.
pub scrollbar_thumb_border: Hsla,
/// The background color of the scrollbar track.
pub scrollbar_track_background: Hsla,
/// The border color of the scrollbar track.
pub scrollbar_track_border: Hsla,
/// The color of the minimap thumb.
pub minimap_thumb_background: Hsla,
/// The color of the minimap thumb when hovered over.
pub minimap_thumb_hover_background: Hsla,
/// The color of the minimap thumb whilst being actively dragged.
pub minimap_thumb_active_background: Hsla,
/// The border color of the minimap thumb.
pub minimap_thumb_border: Hsla,
/// Background color for Vim Normal mode indicator.
pub vim_normal_background: Hsla,
/// Background color for Vim Insert mode indicator.
pub vim_insert_background: Hsla,
/// Background color for Vim Replace mode indicator.
pub vim_replace_background: Hsla,
/// Background color for Vim Visual mode indicator.
pub vim_visual_background: Hsla,
/// Background color for Vim Visual Line mode indicator.
pub vim_visual_line_background: Hsla,
/// Background color for Vim Visual Block mode indicator.
pub vim_visual_block_background: Hsla,
/// Background color for Vim yank highlight.
pub vim_yank_background: Hsla,
/// Foreground color for Helix jump labels.
pub vim_helix_jump_label_foreground: Hsla,
/// Background color for Vim Helix Normal mode indicator.
pub vim_helix_normal_background: Hsla,
/// Background color for Vim Helix Select mode indicator.
pub vim_helix_select_background: Hsla,
/// Foreground color for Vim Normal mode indicator.
pub vim_normal_foreground: Hsla,
/// Foreground color for Vim Insert mode indicator.
pub vim_insert_foreground: Hsla,
/// Foreground color for Vim Replace mode indicator.
pub vim_replace_foreground: Hsla,
/// Foreground color for Vim Visual mode indicator.
pub vim_visual_foreground: Hsla,
/// Foreground color for Vim Visual Line mode indicator.
pub vim_visual_line_foreground: Hsla,
/// Foreground color for Vim Visual Block mode indicator.
pub vim_visual_block_foreground: Hsla,
/// Foreground color for Vim Helix Normal mode indicator.
pub vim_helix_normal_foreground: Hsla,
/// Foreground color for Vim Helix Select mode indicator.
pub vim_helix_select_foreground: Hsla,
// ===
// Editor
// ===
pub editor_foreground: Hsla,
pub editor_background: Hsla,
pub editor_gutter_background: Hsla,
pub editor_subheader_background: Hsla,
pub editor_active_line_background: Hsla,
pub editor_highlighted_line_background: Hsla,
/// Line color of the line a debugger is currently stopped at
pub editor_debugger_active_line_background: Hsla,
/// Text Color. Used for the text of the line number in the editor gutter.
pub editor_line_number: Hsla,
/// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted.
pub editor_active_line_number: Hsla,
/// Text Color. Used for the text of the line number in the editor gutter when the line is hovered over.
pub editor_hover_line_number: Hsla,
/// Text Color. Used to mark invisible characters in the editor.
///
/// Example: spaces, tabs, carriage returns, etc.
pub editor_invisible: Hsla,
pub editor_wrap_guide: Hsla,
pub editor_active_wrap_guide: Hsla,
pub editor_indent_guide: Hsla,
pub editor_indent_guide_active: Hsla,
/// Read-access of a symbol, like reading a variable.
///
/// A document highlight is a range inside a text document which deserves
/// special attention. Usually a document highlight is visualized by changing
/// the background color of its range.
pub editor_document_highlight_read_background: Hsla,
/// Read-access of a symbol, like reading a variable.
///
/// A document highlight is a range inside a text document which deserves
/// special attention. Usually a document highlight is visualized by changing
/// the background color of its range.
pub editor_document_highlight_write_background: Hsla,
/// Highlighted brackets background color.
///
/// Matching brackets in the cursor scope are highlighted with this background color.
pub editor_document_highlight_bracket_background: Hsla,
/// Filled background color for added diff hunk row highlights in the editor.
pub editor_diff_hunk_added_background: Hsla,
/// Hollow background color for added diff hunk row highlights in the editor.
pub editor_diff_hunk_added_hollow_background: Hsla,
/// Hollow border color for added diff hunk row highlights in the editor.
pub editor_diff_hunk_added_hollow_border: Hsla,
/// Filled background color for deleted diff hunk row highlights in the editor.
pub editor_diff_hunk_deleted_background: Hsla,
/// Hollow background color for deleted diff hunk row highlights in the editor.
pub editor_diff_hunk_deleted_hollow_background: Hsla,
/// Hollow border color for deleted diff hunk row highlights in the editor.
pub editor_diff_hunk_deleted_hollow_border: Hsla,
// ===
// Terminal
// ===
/// Terminal layout background color.
pub terminal_background: Hsla,
/// Terminal foreground color.
pub terminal_foreground: Hsla,
/// Bright terminal foreground color.
pub terminal_bright_foreground: Hsla,
/// Dim terminal foreground color.
pub terminal_dim_foreground: Hsla,
/// Terminal ANSI background color.
pub terminal_ansi_background: Hsla,
/// Black ANSI terminal color.
pub terminal_ansi_black: Hsla,
/// Bright black ANSI terminal color.
pub terminal_ansi_bright_black: Hsla,
/// Dim black ANSI terminal color.
pub terminal_ansi_dim_black: Hsla,
/// Red ANSI terminal color.
pub terminal_ansi_red: Hsla,
/// Bright red ANSI terminal color.
pub terminal_ansi_bright_red: Hsla,
/// Dim red ANSI terminal color.
pub terminal_ansi_dim_red: Hsla,
/// Green ANSI terminal color.
pub terminal_ansi_green: Hsla,
/// Bright green ANSI terminal color.
pub terminal_ansi_bright_green: Hsla,
/// Dim green ANSI terminal color.
pub terminal_ansi_dim_green: Hsla,
/// Yellow ANSI terminal color.
pub terminal_ansi_yellow: Hsla,
/// Bright yellow ANSI terminal color.
pub terminal_ansi_bright_yellow: Hsla,
/// Dim yellow ANSI terminal color.
pub terminal_ansi_dim_yellow: Hsla,
/// Blue ANSI terminal color.
pub terminal_ansi_blue: Hsla,
/// Bright blue ANSI terminal color.
pub terminal_ansi_bright_blue: Hsla,
/// Dim blue ANSI terminal color.
pub terminal_ansi_dim_blue: Hsla,
/// Magenta ANSI terminal color.
pub terminal_ansi_magenta: Hsla,
/// Bright magenta ANSI terminal color.
pub terminal_ansi_bright_magenta: Hsla,
/// Dim magenta ANSI terminal color.
pub terminal_ansi_dim_magenta: Hsla,
/// Cyan ANSI terminal color.
pub terminal_ansi_cyan: Hsla,
/// Bright cyan ANSI terminal color.
pub terminal_ansi_bright_cyan: Hsla,
/// Dim cyan ANSI terminal color.
pub terminal_ansi_dim_cyan: Hsla,
/// White ANSI terminal color.
pub terminal_ansi_white: Hsla,
/// Bright white ANSI terminal color.
pub terminal_ansi_bright_white: Hsla,
/// Dim white ANSI terminal color.
pub terminal_ansi_dim_white: Hsla,
/// Represents a link text hover color.
pub link_text_hover: Hsla,
/// Represents an added entry or hunk in vcs, like git.
pub version_control_added: Hsla,
/// Represents a deleted entry in version control systems.
pub version_control_deleted: Hsla,
/// Represents a modified entry in version control systems.
pub version_control_modified: Hsla,
/// Represents a renamed entry in version control systems.
pub version_control_renamed: Hsla,
/// Represents a conflicting entry in version control systems.
pub version_control_conflict: Hsla,
/// Represents an ignored entry in version control systems.
pub version_control_ignored: Hsla,
/// Represents an added word in a word diff.
pub version_control_word_added: Hsla,
/// Represents a deleted word in a word diff.
pub version_control_word_deleted: Hsla,
/// Represents the "ours" region of a merge conflict.
pub version_control_conflict_marker_ours: Hsla,
/// Represents the "theirs" region of a merge conflict.
pub version_control_conflict_marker_theirs: Hsla,
}
#[derive(EnumIter, Debug, Clone, Copy, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum ThemeColorField {
Border,
BorderVariant,
BorderFocused,
BorderSelected,
BorderTransparent,
BorderDisabled,
ElevatedSurfaceBackground,
SurfaceBackground,
Background,
ElementBackground,
ElementHover,
ElementActive,
ElementSelected,
ElementDisabled,
DropTargetBackground,
DropTargetBorder,
GhostElementBackground,
GhostElementHover,
GhostElementActive,
GhostElementSelected,
GhostElementDisabled,
Text,
TextMuted,
TextPlaceholder,
TextDisabled,
TextAccent,
Icon,
IconMuted,
IconDisabled,
IconPlaceholder,
IconAccent,
StatusBarBackground,
TitleBarBackground,
TitleBarInactiveBackground,
ToolbarBackground,
TabBarBackground,
TabInactiveBackground,
TabActiveBackground,
SearchMatchBackground,
SearchActiveMatchBackground,
PanelBackground,
PanelFocusedBorder,
PanelIndentGuide,
PanelIndentGuideHover,
PanelIndentGuideActive,
PanelOverlayBackground,
PanelOverlayHover,
PaneFocusedBorder,
PaneGroupBorder,
ScrollbarThumbBackground,
ScrollbarThumbHoverBackground,
ScrollbarThumbActiveBackground,
ScrollbarThumbBorder,
ScrollbarTrackBackground,
ScrollbarTrackBorder,
MinimapThumbBackground,
MinimapThumbHoverBackground,
MinimapThumbActiveBackground,
MinimapThumbBorder,
EditorForeground,
EditorBackground,
EditorGutterBackground,
EditorSubheaderBackground,
EditorActiveLineBackground,
EditorHighlightedLineBackground,
EditorLineNumber,
EditorActiveLineNumber,
EditorInvisible,
EditorWrapGuide,
EditorActiveWrapGuide,
EditorIndentGuide,
EditorIndentGuideActive,
EditorDocumentHighlightReadBackground,
EditorDocumentHighlightWriteBackground,
EditorDocumentHighlightBracketBackground,
TerminalBackground,
TerminalForeground,
TerminalBrightForeground,
TerminalDimForeground,
TerminalAnsiBackground,
TerminalAnsiBlack,
TerminalAnsiBrightBlack,
TerminalAnsiDimBlack,
TerminalAnsiRed,
TerminalAnsiBrightRed,
TerminalAnsiDimRed,
TerminalAnsiGreen,
TerminalAnsiBrightGreen,
TerminalAnsiDimGreen,
TerminalAnsiYellow,
TerminalAnsiBrightYellow,
TerminalAnsiDimYellow,
TerminalAnsiBlue,
TerminalAnsiBrightBlue,
TerminalAnsiDimBlue,
TerminalAnsiMagenta,
TerminalAnsiBrightMagenta,
TerminalAnsiDimMagenta,
TerminalAnsiCyan,
TerminalAnsiBrightCyan,
TerminalAnsiDimCyan,
TerminalAnsiWhite,
TerminalAnsiBrightWhite,
TerminalAnsiDimWhite,
LinkTextHover,
VersionControlAdded,
VersionControlDeleted,
VersionControlModified,
VersionControlRenamed,
VersionControlConflict,
VersionControlIgnored,
}
impl ThemeColors {
pub fn color(&self, field: ThemeColorField) -> Hsla {
match field {
ThemeColorField::Border => self.border,
ThemeColorField::BorderVariant => self.border_variant,
ThemeColorField::BorderFocused => self.border_focused,
ThemeColorField::BorderSelected => self.border_selected,
ThemeColorField::BorderTransparent => self.border_transparent,
ThemeColorField::BorderDisabled => self.border_disabled,
ThemeColorField::ElevatedSurfaceBackground => self.elevated_surface_background,
ThemeColorField::SurfaceBackground => self.surface_background,
ThemeColorField::Background => self.background,
ThemeColorField::ElementBackground => self.element_background,
ThemeColorField::ElementHover => self.element_hover,
ThemeColorField::ElementActive => self.element_active,
ThemeColorField::ElementSelected => self.element_selected,
ThemeColorField::ElementDisabled => self.element_disabled,
ThemeColorField::DropTargetBackground => self.drop_target_background,
ThemeColorField::DropTargetBorder => self.drop_target_border,
ThemeColorField::GhostElementBackground => self.ghost_element_background,
ThemeColorField::GhostElementHover => self.ghost_element_hover,
ThemeColorField::GhostElementActive => self.ghost_element_active,
ThemeColorField::GhostElementSelected => self.ghost_element_selected,
ThemeColorField::GhostElementDisabled => self.ghost_element_disabled,
ThemeColorField::Text => self.text,
ThemeColorField::TextMuted => self.text_muted,
ThemeColorField::TextPlaceholder => self.text_placeholder,
ThemeColorField::TextDisabled => self.text_disabled,
ThemeColorField::TextAccent => self.text_accent,
ThemeColorField::Icon => self.icon,
ThemeColorField::IconMuted => self.icon_muted,
ThemeColorField::IconDisabled => self.icon_disabled,
ThemeColorField::IconPlaceholder => self.icon_placeholder,
ThemeColorField::IconAccent => self.icon_accent,
ThemeColorField::StatusBarBackground => self.status_bar_background,
ThemeColorField::TitleBarBackground => self.title_bar_background,
ThemeColorField::TitleBarInactiveBackground => self.title_bar_inactive_background,
ThemeColorField::ToolbarBackground => self.toolbar_background,
ThemeColorField::TabBarBackground => self.tab_bar_background,
ThemeColorField::TabInactiveBackground => self.tab_inactive_background,
ThemeColorField::TabActiveBackground => self.tab_active_background,
ThemeColorField::SearchMatchBackground => self.search_match_background,
ThemeColorField::SearchActiveMatchBackground => self.search_active_match_background,
ThemeColorField::PanelBackground => self.panel_background,
ThemeColorField::PanelFocusedBorder => self.panel_focused_border,
ThemeColorField::PanelIndentGuide => self.panel_indent_guide,
ThemeColorField::PanelIndentGuideHover => self.panel_indent_guide_hover,
ThemeColorField::PanelIndentGuideActive => self.panel_indent_guide_active,
ThemeColorField::PanelOverlayBackground => self.panel_overlay_background,
ThemeColorField::PanelOverlayHover => self.panel_overlay_hover,
ThemeColorField::PaneFocusedBorder => self.pane_focused_border,
ThemeColorField::PaneGroupBorder => self.pane_group_border,
ThemeColorField::ScrollbarThumbBackground => self.scrollbar_thumb_background,
ThemeColorField::ScrollbarThumbHoverBackground => self.scrollbar_thumb_hover_background,
ThemeColorField::ScrollbarThumbActiveBackground => {
self.scrollbar_thumb_active_background
}
ThemeColorField::ScrollbarThumbBorder => self.scrollbar_thumb_border,
ThemeColorField::ScrollbarTrackBackground => self.scrollbar_track_background,
ThemeColorField::ScrollbarTrackBorder => self.scrollbar_track_border,
ThemeColorField::MinimapThumbBackground => self.minimap_thumb_background,
ThemeColorField::MinimapThumbHoverBackground => self.minimap_thumb_hover_background,
ThemeColorField::MinimapThumbActiveBackground => self.minimap_thumb_active_background,
ThemeColorField::MinimapThumbBorder => self.minimap_thumb_border,
ThemeColorField::EditorForeground => self.editor_foreground,
ThemeColorField::EditorBackground => self.editor_background,
ThemeColorField::EditorGutterBackground => self.editor_gutter_background,
ThemeColorField::EditorSubheaderBackground => self.editor_subheader_background,
ThemeColorField::EditorActiveLineBackground => self.editor_active_line_background,
ThemeColorField::EditorHighlightedLineBackground => {
self.editor_highlighted_line_background
}
ThemeColorField::EditorLineNumber => self.editor_line_number,
ThemeColorField::EditorActiveLineNumber => self.editor_active_line_number,
ThemeColorField::EditorInvisible => self.editor_invisible,
ThemeColorField::EditorWrapGuide => self.editor_wrap_guide,
ThemeColorField::EditorActiveWrapGuide => self.editor_active_wrap_guide,
ThemeColorField::EditorIndentGuide => self.editor_indent_guide,
ThemeColorField::EditorIndentGuideActive => self.editor_indent_guide_active,
ThemeColorField::EditorDocumentHighlightReadBackground => {
self.editor_document_highlight_read_background
}
ThemeColorField::EditorDocumentHighlightWriteBackground => {
self.editor_document_highlight_write_background
}
ThemeColorField::EditorDocumentHighlightBracketBackground => {
self.editor_document_highlight_bracket_background
}
ThemeColorField::TerminalBackground => self.terminal_background,
ThemeColorField::TerminalForeground => self.terminal_foreground,
ThemeColorField::TerminalBrightForeground => self.terminal_bright_foreground,
ThemeColorField::TerminalDimForeground => self.terminal_dim_foreground,
ThemeColorField::TerminalAnsiBackground => self.terminal_ansi_background,
ThemeColorField::TerminalAnsiBlack => self.terminal_ansi_black,
ThemeColorField::TerminalAnsiBrightBlack => self.terminal_ansi_bright_black,
ThemeColorField::TerminalAnsiDimBlack => self.terminal_ansi_dim_black,
ThemeColorField::TerminalAnsiRed => self.terminal_ansi_red,
ThemeColorField::TerminalAnsiBrightRed => self.terminal_ansi_bright_red,
ThemeColorField::TerminalAnsiDimRed => self.terminal_ansi_dim_red,
ThemeColorField::TerminalAnsiGreen => self.terminal_ansi_green,
ThemeColorField::TerminalAnsiBrightGreen => self.terminal_ansi_bright_green,
ThemeColorField::TerminalAnsiDimGreen => self.terminal_ansi_dim_green,
ThemeColorField::TerminalAnsiYellow => self.terminal_ansi_yellow,
ThemeColorField::TerminalAnsiBrightYellow => self.terminal_ansi_bright_yellow,
ThemeColorField::TerminalAnsiDimYellow => self.terminal_ansi_dim_yellow,
ThemeColorField::TerminalAnsiBlue => self.terminal_ansi_blue,
ThemeColorField::TerminalAnsiBrightBlue => self.terminal_ansi_bright_blue,
ThemeColorField::TerminalAnsiDimBlue => self.terminal_ansi_dim_blue,
ThemeColorField::TerminalAnsiMagenta => self.terminal_ansi_magenta,
ThemeColorField::TerminalAnsiBrightMagenta => self.terminal_ansi_bright_magenta,
ThemeColorField::TerminalAnsiDimMagenta => self.terminal_ansi_dim_magenta,
ThemeColorField::TerminalAnsiCyan => self.terminal_ansi_cyan,
ThemeColorField::TerminalAnsiBrightCyan => self.terminal_ansi_bright_cyan,
ThemeColorField::TerminalAnsiDimCyan => self.terminal_ansi_dim_cyan,
ThemeColorField::TerminalAnsiWhite => self.terminal_ansi_white,
ThemeColorField::TerminalAnsiBrightWhite => self.terminal_ansi_bright_white,
ThemeColorField::TerminalAnsiDimWhite => self.terminal_ansi_dim_white,
ThemeColorField::LinkTextHover => self.link_text_hover,
ThemeColorField::VersionControlAdded => self.version_control_added,
ThemeColorField::VersionControlDeleted => self.version_control_deleted,
ThemeColorField::VersionControlModified => self.version_control_modified,
ThemeColorField::VersionControlRenamed => self.version_control_renamed,
ThemeColorField::VersionControlConflict => self.version_control_conflict,
ThemeColorField::VersionControlIgnored => self.version_control_ignored,
}
}
pub fn iter(&self) -> impl Iterator<Item = (ThemeColorField, Hsla)> + '_ {
ThemeColorField::iter().map(move |field| (field, self.color(field)))
}
pub fn to_vec(&self) -> Vec<(ThemeColorField, Hsla)> {
self.iter().collect()
}
}
pub fn all_theme_colors(cx: &mut App) -> Vec<(Hsla, SharedString)> {
let theme = cx.theme();
ThemeColorField::iter()
.map(|field| {
let color = theme.colors().color(field);
let name = field.as_ref().to_string();
(color, SharedString::from(name))
})
.collect()
}
#[derive(Refineable, Clone, Debug, PartialEq)]
pub struct ThemeStyles {
/// The background appearance of the window.
pub window_background_appearance: WindowBackgroundAppearance,
pub system: SystemColors,
/// An array of colors used for theme elements that iterate through a series of colors.
///
/// Example: Player colors, rainbow brackets and indent guides, etc.
pub accents: AccentColors,
#[refineable]
pub colors: ThemeColors,
#[refineable]
pub status: StatusColors,
pub player: PlayerColors,
pub syntax: Arc<SyntaxTheme>,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn override_a_single_theme_color() {
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff).into();
assert_ne!(colors.text, magenta);
let overrides = ThemeColorsRefinement {
text: Some(magenta),
..Default::default()
};
colors.refine(&overrides);
assert_eq!(colors.text, magenta);
}
#[test]
fn override_multiple_theme_colors() {
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff).into();
let green: Hsla = gpui::rgb(0x00ff00).into();
assert_ne!(colors.text, magenta);
assert_ne!(colors.background, green);
let overrides = ThemeColorsRefinement {
text: Some(magenta),
background: Some(green),
..Default::default()
};
colors.refine(&overrides);
assert_eq!(colors.text, magenta);
assert_eq!(colors.background, green);
}
#[test]
fn deserialize_theme_colors_refinement_from_json() {
let colors: ThemeColorsRefinement = serde_json::from_value(json!({
"background": "#ff00ff",
"text": "#ff0000"
}))
.unwrap();
assert_eq!(colors.background, Some(gpui::rgb(0xff00ff).into()));
assert_eq!(colors.text, Some(gpui::rgb(0xff0000).into()));
}
}

View File

@@ -0,0 +1,151 @@
#![allow(missing_docs)]
use gpui::Hsla;
use serde::Deserialize;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq)]
pub struct PlayerColor {
pub cursor: Hsla,
pub background: Hsla,
pub selection: Hsla,
}
/// A collection of colors that are used to color players in the editor.
///
/// The first color is always the local player's color, usually a blue.
///
/// The rest of the default colors crisscross back and forth on the
/// color wheel so that the colors are as distinct as possible.
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct PlayerColors(pub Vec<PlayerColor>);
impl Default for PlayerColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// TODO "Find a way to not need this for Refinable"
fn default() -> Self {
Self::dark()
}
}
impl PlayerColors {
pub fn dark() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
pub fn light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
impl PlayerColors {
pub fn local(&self) -> PlayerColor {
*self.0.first().unwrap()
}
pub fn agent(&self) -> PlayerColor {
*self.0.last().unwrap()
}
pub fn absent(&self) -> PlayerColor {
*self.0.last().unwrap()
}
pub fn read_only(&self) -> PlayerColor {
let local = self.local();
PlayerColor {
cursor: local.cursor.grayscale(),
background: local.background.grayscale(),
selection: local.selection.grayscale(),
}
}
pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
let len = self.0.len() - 1;
self.0[(participant_index as usize % len) + 1]
}
}

View File

@@ -0,0 +1,191 @@
#![allow(missing_docs)]
use gpui::Hsla;
use refineable::Refineable;
use crate::{blue, grass, neutral, red, yellow};
#[derive(Refineable, Clone, Debug, PartialEq)]
#[refineable(Debug, serde::Deserialize)]
pub struct StatusColors {
/// Indicates some kind of conflict, like a file changed on disk while it was open, or
/// merge conflicts in a Git repository.
pub conflict: Hsla,
pub conflict_background: Hsla,
pub conflict_border: Hsla,
/// Indicates something new, like a new file added to a Git repository.
pub created: Hsla,
pub created_background: Hsla,
pub created_border: Hsla,
/// Indicates that something no longer exists, like a deleted file.
pub deleted: Hsla,
pub deleted_background: Hsla,
pub deleted_border: Hsla,
/// Indicates a system error, a failed operation or a diagnostic error.
pub error: Hsla,
pub error_background: Hsla,
pub error_border: Hsla,
/// Represents a hidden status, such as a file being hidden in a file tree.
pub hidden: Hsla,
pub hidden_background: Hsla,
pub hidden_border: Hsla,
/// Indicates a hint or some kind of additional information.
pub hint: Hsla,
pub hint_background: Hsla,
pub hint_border: Hsla,
/// Indicates that something is deliberately ignored, such as a file or operation ignored by Git.
pub ignored: Hsla,
pub ignored_background: Hsla,
pub ignored_border: Hsla,
/// Represents informational status updates or messages.
pub info: Hsla,
pub info_background: Hsla,
pub info_border: Hsla,
/// Indicates a changed or altered status, like a file that has been edited.
pub modified: Hsla,
pub modified_background: Hsla,
pub modified_border: Hsla,
/// Indicates something that is predicted, like automatic code completion, or generated code.
pub predictive: Hsla,
pub predictive_background: Hsla,
pub predictive_border: Hsla,
/// Represents a renamed status, such as a file that has been renamed.
pub renamed: Hsla,
pub renamed_background: Hsla,
pub renamed_border: Hsla,
/// Indicates a successful operation or task completion.
pub success: Hsla,
pub success_background: Hsla,
pub success_border: Hsla,
/// Indicates some kind of unreachable status, like a block of code that can never be reached.
pub unreachable: Hsla,
pub unreachable_background: Hsla,
pub unreachable_border: Hsla,
/// Represents a warning status, like an operation that is about to fail.
pub warning: Hsla,
pub warning_background: Hsla,
pub warning_border: Hsla,
}
pub struct DiagnosticColors {
pub error: Hsla,
pub warning: Hsla,
pub info: Hsla,
}
impl StatusColors {
pub fn dark() -> Self {
Self {
conflict: red().dark().step_9(),
conflict_background: red().dark().step_9(),
conflict_border: red().dark().step_9(),
created: grass().dark().step_9(),
created_background: grass().dark().step_9().opacity(0.25),
created_border: grass().dark().step_9(),
deleted: red().dark().step_9(),
deleted_background: red().dark().step_9().opacity(0.25),
deleted_border: red().dark().step_9(),
error: red().dark().step_9(),
error_background: red().dark().step_9(),
error_border: red().dark().step_9(),
hidden: neutral().dark().step_9(),
hidden_background: neutral().dark().step_9(),
hidden_border: neutral().dark().step_9(),
hint: blue().dark().step_9(),
hint_background: blue().dark().step_9(),
hint_border: blue().dark().step_9(),
ignored: neutral().dark().step_9(),
ignored_background: neutral().dark().step_9(),
ignored_border: neutral().dark().step_9(),
info: blue().dark().step_9(),
info_background: blue().dark().step_9(),
info_border: blue().dark().step_9(),
modified: yellow().dark().step_9(),
modified_background: yellow().dark().step_9().opacity(0.25),
modified_border: yellow().dark().step_9(),
predictive: neutral().dark_alpha().step_9(),
predictive_background: neutral().dark_alpha().step_9(),
predictive_border: neutral().dark_alpha().step_9(),
renamed: blue().dark().step_9(),
renamed_background: blue().dark().step_9(),
renamed_border: blue().dark().step_9(),
success: grass().dark().step_9(),
success_background: grass().dark().step_9(),
success_border: grass().dark().step_9(),
unreachable: neutral().dark().step_10(),
unreachable_background: neutral().dark().step_10(),
unreachable_border: neutral().dark().step_10(),
warning: yellow().dark().step_9(),
warning_background: yellow().dark().step_9(),
warning_border: yellow().dark().step_9(),
}
}
pub fn light() -> Self {
Self {
conflict: red().light().step_9(),
conflict_background: red().light().step_9(),
conflict_border: red().light().step_9(),
created: grass().light().step_9(),
created_background: grass().light().step_9(),
created_border: grass().light().step_9(),
deleted: red().light().step_9(),
deleted_background: red().light().step_9(),
deleted_border: red().light().step_9(),
error: red().light().step_9(),
error_background: red().light().step_9(),
error_border: red().light().step_9(),
hidden: neutral().light().step_9(),
hidden_background: neutral().light().step_9(),
hidden_border: neutral().light().step_9(),
hint: blue().light().step_9(),
hint_background: blue().light().step_9(),
hint_border: blue().light().step_9(),
ignored: neutral().light().step_9(),
ignored_background: neutral().light().step_9(),
ignored_border: neutral().light().step_9(),
info: blue().light().step_9(),
info_background: blue().light().step_9(),
info_border: blue().light().step_9(),
modified: yellow().light().step_9(),
modified_background: yellow().light().step_9(),
modified_border: yellow().light().step_9(),
predictive: neutral().light_alpha().step_9(),
predictive_background: neutral().light_alpha().step_9(),
predictive_border: neutral().light_alpha().step_9(),
renamed: blue().light().step_9(),
renamed_background: blue().light().step_9(),
renamed_border: blue().light().step_9(),
success: grass().light().step_9(),
success_background: grass().light().step_9(),
success_border: grass().light().step_9(),
unreachable: neutral().light().step_10(),
unreachable_background: neutral().light().step_10(),
unreachable_border: neutral().light().step_10(),
warning: yellow().light().step_9(),
warning_background: yellow().light().step_9(),
warning_border: yellow().light().step_9(),
}
}
pub fn diagnostic(&self) -> DiagnosticColors {
DiagnosticColors {
error: self.error,
warning: self.warning,
info: self.info,
}
}
}

View File

@@ -0,0 +1 @@
pub use syntax_theme::SyntaxTheme;

View File

@@ -0,0 +1,22 @@
#![allow(missing_docs)]
use gpui::{Hsla, hsla};
#[derive(Clone, Debug, PartialEq)]
pub struct SystemColors {
pub transparent: Hsla,
pub mac_os_traffic_light_red: Hsla,
pub mac_os_traffic_light_yellow: Hsla,
pub mac_os_traffic_light_green: Hsla,
}
impl Default for SystemColors {
fn default() -> Self {
Self {
transparent: hsla(0.0, 0.0, 0.0, 0.0),
mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
}
}
}

324
crates/theme/src/theme.rs Normal file
View File

@@ -0,0 +1,324 @@
#![deny(missing_docs)]
//! # Theme
//!
//! This crate provides the theme system for Zed.
//!
//! ## Overview
//!
//! A theme is a collection of colors used to build a consistent appearance for UI components across the application.
mod default_colors;
mod fallback_themes;
mod font_family_cache;
mod icon_theme;
mod icon_theme_schema;
mod registry;
mod scale;
mod schema;
mod styles;
mod theme_settings_provider;
mod ui_density;
use std::sync::Arc;
use gpui::BorrowAppContext;
use gpui::Global;
use gpui::{
App, AssetSource, Hsla, Pixels, SharedString, WindowAppearance, WindowBackgroundAppearance, px,
};
use serde::Deserialize;
pub use crate::default_colors::*;
pub use crate::fallback_themes::{apply_status_color_defaults, apply_theme_color_defaults};
pub use crate::font_family_cache::*;
pub use crate::icon_theme::*;
pub use crate::icon_theme_schema::*;
pub use crate::registry::*;
pub use crate::scale::*;
pub use crate::schema::*;
pub use crate::styles::*;
pub use crate::theme_settings_provider::*;
pub use crate::ui_density::*;
/// The name of the default dark theme.
pub const DEFAULT_DARK_THEME: &str = "One Dark";
/// Defines window border radius for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
/// Defines window shadow size for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_SHADOW: Pixels = px(10.0);
/// The appearance of the theme.
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
pub enum Appearance {
/// A light appearance.
Light,
/// A dark appearance.
Dark,
}
impl Appearance {
/// Returns whether the appearance is light.
pub fn is_light(&self) -> bool {
match self {
Self::Light => true,
Self::Dark => false,
}
}
}
impl From<WindowAppearance> for Appearance {
fn from(value: WindowAppearance) -> Self {
match value {
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
}
}
}
/// Which themes should be loaded. This is used primarily for testing.
pub enum LoadThemes {
/// Only load the base theme.
///
/// No user themes will be loaded.
JustBase,
/// Load all of the built-in themes.
All(Box<dyn AssetSource>),
}
/// Initialize the theme system with default themes.
///
/// This sets up the [`ThemeRegistry`], [`FontFamilyCache`], [`SystemAppearance`],
/// and [`GlobalTheme`] with the default dark theme. It does NOT load bundled
/// themes from JSON or integrate with settings — use `theme_settings::init` for that.
pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
SystemAppearance::init(cx);
let assets = match themes_to_load {
LoadThemes::JustBase => Box::new(()) as Box<dyn AssetSource>,
LoadThemes::All(assets) => assets,
};
ThemeRegistry::set_global(assets, cx);
FontFamilyCache::init_global(cx);
let themes = ThemeRegistry::default_global(cx);
let theme = themes.get(DEFAULT_DARK_THEME).unwrap_or_else(|_| {
themes
.list()
.into_iter()
.next()
.map(|m| themes.get(&m.name).unwrap())
.unwrap()
});
let icon_theme = themes.default_icon_theme().unwrap();
cx.set_global(GlobalTheme { theme, icon_theme });
}
/// Implementing this trait allows accessing the active theme.
pub trait ActiveTheme {
/// Returns the active theme.
fn theme(&self) -> &Arc<Theme>;
}
impl ActiveTheme for App {
fn theme(&self) -> &Arc<Theme> {
GlobalTheme::theme(self)
}
}
/// The appearance of the system.
#[derive(Debug, Clone, Copy)]
pub struct SystemAppearance(pub Appearance);
impl std::ops::Deref for SystemAppearance {
type Target = Appearance;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for SystemAppearance {
fn default() -> Self {
Self(Appearance::Dark)
}
}
#[derive(Default)]
struct GlobalSystemAppearance(SystemAppearance);
impl std::ops::DerefMut for GlobalSystemAppearance {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::ops::Deref for GlobalSystemAppearance {
type Target = SystemAppearance;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Global for GlobalSystemAppearance {}
impl SystemAppearance {
/// Initializes the [`SystemAppearance`] for the application.
pub fn init(cx: &mut App) {
*cx.default_global::<GlobalSystemAppearance>() =
GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
}
/// Returns the global [`SystemAppearance`].
pub fn global(cx: &App) -> Self {
cx.global::<GlobalSystemAppearance>().0
}
/// Returns a mutable reference to the global [`SystemAppearance`].
pub fn global_mut(cx: &mut App) -> &mut Self {
cx.global_mut::<GlobalSystemAppearance>()
}
}
/// A theme family is a grouping of themes under a single name.
///
/// For example, the "One" theme family contains the "One Light" and "One Dark" themes.
///
/// It can also be used to package themes with many variants.
///
/// For example, the "Atelier" theme family contains "Cave", "Dune", "Estuary", "Forest", "Heath", etc.
pub struct ThemeFamily {
/// The unique identifier for the theme family.
pub id: String,
/// The name of the theme family. This will be displayed in the UI, such as when adding or removing a theme family.
pub name: SharedString,
/// The author of the theme family.
pub author: SharedString,
/// The [Theme]s in the family.
pub themes: Vec<Theme>,
/// The color scales used by the themes in the family.
/// Note: This will be removed in the future.
pub scales: ColorScales,
}
/// A theme is the primary mechanism for defining the appearance of the UI.
#[derive(Clone, Debug, PartialEq)]
pub struct Theme {
/// The unique identifier for the theme.
pub id: String,
/// The name of the theme.
pub name: SharedString,
/// The appearance of the theme (light or dark).
pub appearance: Appearance,
/// The colors and other styles for the theme.
pub styles: ThemeStyles,
}
impl Theme {
/// Returns the [`SystemColors`] for the theme.
#[inline(always)]
pub fn system(&self) -> &SystemColors {
&self.styles.system
}
/// Returns the [`AccentColors`] for the theme.
#[inline(always)]
pub fn accents(&self) -> &AccentColors {
&self.styles.accents
}
/// Returns the [`PlayerColors`] for the theme.
#[inline(always)]
pub fn players(&self) -> &PlayerColors {
&self.styles.player
}
/// Returns the [`ThemeColors`] for the theme.
#[inline(always)]
pub fn colors(&self) -> &ThemeColors {
&self.styles.colors
}
/// Returns the [`SyntaxTheme`] for the theme.
#[inline(always)]
pub fn syntax(&self) -> &Arc<SyntaxTheme> {
&self.styles.syntax
}
/// Returns the [`StatusColors`] for the theme.
#[inline(always)]
pub fn status(&self) -> &StatusColors {
&self.styles.status
}
/// Returns the [`Appearance`] for the theme.
#[inline(always)]
pub fn appearance(&self) -> Appearance {
self.appearance
}
/// Returns the [`WindowBackgroundAppearance`] for the theme.
#[inline(always)]
pub fn window_background_appearance(&self) -> WindowBackgroundAppearance {
self.styles.window_background_appearance
}
/// Darkens the color by reducing its lightness.
/// The resulting lightness is clamped to ensure it doesn't go below 0.0.
///
/// The first value darkens light appearance mode, the second darkens appearance dark mode.
///
/// Note: This is a tentative solution and may be replaced with a more robust color system.
pub fn darken(&self, color: Hsla, light_amount: f32, dark_amount: f32) -> Hsla {
let amount = match self.appearance {
Appearance::Light => light_amount,
Appearance::Dark => dark_amount,
};
let mut hsla = color;
hsla.l = (hsla.l - amount).max(0.0);
hsla
}
}
/// Deserializes an icon theme from the given bytes.
pub fn deserialize_icon_theme(bytes: &[u8]) -> anyhow::Result<IconThemeFamilyContent> {
let icon_theme_family: IconThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
Ok(icon_theme_family)
}
/// The active theme.
pub struct GlobalTheme {
theme: Arc<Theme>,
icon_theme: Arc<IconTheme>,
}
impl Global for GlobalTheme {}
impl GlobalTheme {
/// Creates a new [`GlobalTheme`] with the given theme and icon theme.
pub fn new(theme: Arc<Theme>, icon_theme: Arc<IconTheme>) -> Self {
Self { theme, icon_theme }
}
/// Updates the active theme.
pub fn update_theme(cx: &mut App, theme: Arc<Theme>) {
cx.update_global::<Self, _>(|this, _| this.theme = theme);
}
/// Updates the active icon theme.
pub fn update_icon_theme(cx: &mut App, icon_theme: Arc<IconTheme>) {
cx.update_global::<Self, _>(|this, _| this.icon_theme = icon_theme);
}
/// Returns the active theme.
pub fn theme(cx: &App) -> &Arc<Theme> {
&cx.global::<Self>().theme
}
/// Returns the active icon theme.
pub fn icon_theme(cx: &App) -> &Arc<IconTheme> {
&cx.global::<Self>().icon_theme
}
}

View File

@@ -0,0 +1,43 @@
use gpui::{App, Font, Global, Pixels};
use crate::UiDensity;
/// Trait for providing theme-related settings (fonts, font sizes, UI density)
/// without coupling to the concrete settings infrastructure.
///
/// A concrete implementation is registered as a global by the `theme_settings` crate.
pub trait ThemeSettingsProvider: Send + Sync + 'static {
/// Returns the font used for UI elements.
fn ui_font<'a>(&'a self, cx: &'a App) -> &'a Font;
/// Returns the font used for buffers and the terminal.
fn buffer_font<'a>(&'a self, cx: &'a App) -> &'a Font;
/// Returns the UI font size in pixels.
fn ui_font_size(&self, cx: &App) -> Pixels;
/// Returns the buffer font size in pixels.
fn buffer_font_size(&self, cx: &App) -> Pixels;
/// Returns the current UI density setting.
fn ui_density(&self, cx: &App) -> UiDensity;
}
struct GlobalThemeSettingsProvider(Box<dyn ThemeSettingsProvider>);
impl Global for GlobalThemeSettingsProvider {}
/// Registers the global [`ThemeSettingsProvider`] implementation.
///
/// This should be called during application initialization by the crate
/// that owns the concrete theme settings (e.g. `theme_settings`).
pub fn set_theme_settings_provider(provider: Box<dyn ThemeSettingsProvider>, cx: &mut App) {
cx.set_global(GlobalThemeSettingsProvider(provider));
}
/// Returns the global [`ThemeSettingsProvider`].
///
/// Panics if no provider has been registered via [`set_theme_settings_provider`].
pub fn theme_settings(cx: &App) -> &dyn ThemeSettingsProvider {
&*cx.global::<GlobalThemeSettingsProvider>().0
}

View File

@@ -0,0 +1,65 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Specifies the density of the UI.
/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
#[derive(
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Clone,
Copy,
Serialize,
Deserialize,
JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum UiDensity {
/// A denser UI with tighter spacing and smaller elements.
#[serde(alias = "compact")]
Compact,
#[default]
#[serde(alias = "default")]
/// The default UI density.
Default,
#[serde(alias = "comfortable")]
/// A looser UI with more spacing and larger elements.
Comfortable,
}
impl UiDensity {
/// The spacing ratio of a given density.
/// TODO: Standardize usage throughout the app or remove
pub fn spacing_ratio(self) -> f32 {
match self {
UiDensity::Compact => 0.75,
UiDensity::Default => 1.0,
UiDensity::Comfortable => 1.25,
}
}
}
impl From<String> for UiDensity {
fn from(s: String) -> Self {
match s.as_str() {
"compact" => Self::Compact,
"default" => Self::Default,
"comfortable" => Self::Comfortable,
_ => Self::default(),
}
}
}
impl From<UiDensity> for String {
fn from(val: UiDensity) -> Self {
match val {
UiDensity::Compact => "compact".to_string(),
UiDensity::Default => "default".to_string(),
UiDensity::Comfortable => "comfortable".to_string(),
}
}
}