logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
36
crates/theme/Cargo.toml
Normal file
36
crates/theme/Cargo.toml
Normal 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
1
crates/theme/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
2534
crates/theme/src/default_colors.rs
Normal file
2534
crates/theme/src/default_colors.rs
Normal file
File diff suppressed because it is too large
Load Diff
392
crates/theme/src/fallback_themes.rs
Normal file
392
crates/theme/src/fallback_themes.rs
Normal 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()),
|
||||
])),
|
||||
},
|
||||
}
|
||||
}
|
||||
93
crates/theme/src/font_family_cache.rs
Normal file
93
crates/theme/src/font_family_cache.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
453
crates/theme/src/icon_theme.rs
Normal file
453
crates/theme/src/icon_theme.rs
Normal 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()
|
||||
}
|
||||
50
crates/theme/src/icon_theme_schema.rs
Normal file
50
crates/theme/src/icon_theme_schema.rs
Normal 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,
|
||||
}
|
||||
331
crates/theme/src/registry.rs
Normal file
331
crates/theme/src/registry.rs
Normal 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
298
crates/theme/src/scale.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
30
crates/theme/src/schema.rs
Normal file
30
crates/theme/src/schema.rs
Normal 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)
|
||||
}
|
||||
13
crates/theme/src/styles.rs
Normal file
13
crates/theme/src/styles.rs
Normal 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::*;
|
||||
68
crates/theme/src/styles/accents.rs
Normal file
68
crates/theme/src/styles/accents.rs
Normal 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()]
|
||||
}
|
||||
}
|
||||
685
crates/theme/src/styles/colors.rs
Normal file
685
crates/theme/src/styles/colors.rs
Normal 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()));
|
||||
}
|
||||
}
|
||||
151
crates/theme/src/styles/players.rs
Normal file
151
crates/theme/src/styles/players.rs
Normal 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]
|
||||
}
|
||||
}
|
||||
191
crates/theme/src/styles/status.rs
Normal file
191
crates/theme/src/styles/status.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/theme/src/styles/syntax.rs
Normal file
1
crates/theme/src/styles/syntax.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub use syntax_theme::SyntaxTheme;
|
||||
22
crates/theme/src/styles/system.rs
Normal file
22
crates/theme/src/styles/system.rs
Normal 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
324
crates/theme/src/theme.rs
Normal 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
|
||||
}
|
||||
}
|
||||
43
crates/theme/src/theme_settings_provider.rs
Normal file
43
crates/theme/src/theme_settings_provider.rs
Normal 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
|
||||
}
|
||||
65
crates/theme/src/ui_density.rs
Normal file
65
crates/theme/src/ui_density.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user