simplify theme crate to builtin-only registry
This commit is contained in:
@@ -7,7 +7,6 @@ publish.workspace = true
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
gpui-base.workspace = true
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -14,6 +13,17 @@ pub fn builtin_registry() -> &'static ThemeRegistry {
|
||||
REGISTRY.get_or_init(|| ThemeRegistry {
|
||||
families: vec![
|
||||
family("zeron", "Zeron", vec![zeron_light(), zeron_dark()]),
|
||||
family("dracula", "Dracula", vec![dracula()]),
|
||||
family("github", "GitHub", vec![github_light(), github_dark()]),
|
||||
family("ayu", "Ayu", vec![ayu_light(), ayu_dark(), ayu_mirage()]),
|
||||
family("gruvbox", "Gruvbox", vec![gruvbox_light(), gruvbox_dark()]),
|
||||
family("nord", "Nord", vec![nord()]),
|
||||
family("one-dark-pro", "One Dark Pro", vec![one_dark_pro()]),
|
||||
family("atom-one-dark", "Atom One Dark", vec![atom_one_dark()]),
|
||||
family("palenight", "Palenight", vec![palenight()]),
|
||||
family("synthwave-84", "SynthWave '84", vec![synthwave_84()]),
|
||||
family("cobalt2", "Cobalt2", vec![cobalt2()]),
|
||||
family("andromeda", "Andromeda", vec![andromeda()]),
|
||||
family(
|
||||
"vscode-default",
|
||||
"VS Code Default",
|
||||
@@ -29,37 +39,26 @@ pub fn builtin_registry() -> &'static ThemeRegistry {
|
||||
"Tokyo Night",
|
||||
vec![tokyo_light(), tokyo_dark()],
|
||||
),
|
||||
family("dracula", "Dracula", vec![dracula()]),
|
||||
family("github", "GitHub", vec![github_light(), github_dark()]),
|
||||
family("ayu", "Ayu", vec![ayu_light(), ayu_dark(), ayu_mirage()]),
|
||||
family("gruvbox", "Gruvbox", vec![gruvbox_light(), gruvbox_dark()]),
|
||||
family(
|
||||
"rose-pine",
|
||||
"Rosé Pine",
|
||||
vec![rose_pine_dawn(), rose_pine_moon()],
|
||||
),
|
||||
family("nord", "Nord", vec![nord()]),
|
||||
family("one-dark-pro", "One Dark Pro", vec![one_dark_pro()]),
|
||||
family("atom-one-dark", "Atom One Dark", vec![atom_one_dark()]),
|
||||
family(
|
||||
"night-owl",
|
||||
"Night Owl",
|
||||
vec![night_owl_light(), night_owl()],
|
||||
),
|
||||
family(
|
||||
"winter-is-coming",
|
||||
"Winter is Coming",
|
||||
vec![winter_light(), winter_dark_blue()],
|
||||
),
|
||||
family("palenight", "Palenight", vec![palenight()]),
|
||||
family("synthwave-84", "SynthWave '84", vec![synthwave_84()]),
|
||||
family(
|
||||
"shades-of-purple",
|
||||
"Shades of Purple",
|
||||
vec![shades_of_purple()],
|
||||
),
|
||||
family("cobalt2", "Cobalt2", vec![cobalt2()]),
|
||||
family("andromeda", "Andromeda", vec![andromeda()]),
|
||||
family(
|
||||
"winter-is-coming",
|
||||
"Winter is Coming",
|
||||
vec![winter_light(), winter_dark_blue()],
|
||||
),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::fmt::Debug;
|
||||
use gpui::Axis;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A enum for defining the side of the element.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Side {
|
||||
#[serde(rename = "left")]
|
||||
@@ -13,20 +12,17 @@ pub enum Side {
|
||||
}
|
||||
|
||||
impl Side {
|
||||
/// Returns true if the side is left.
|
||||
#[inline]
|
||||
pub fn is_left(&self) -> bool {
|
||||
matches!(self, Self::Left)
|
||||
}
|
||||
|
||||
/// Returns true if the side is right.
|
||||
#[inline]
|
||||
pub fn is_right(&self) -> bool {
|
||||
matches!(self, Self::Right)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to extend the [`Axis`] enum with utility methods.
|
||||
pub trait AxisExt {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_horizontal(self) -> bool;
|
||||
@@ -46,18 +42,12 @@ impl AxisExt for Axis {
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for defining the edges of an element.
|
||||
///
|
||||
/// A extend version of [`gpui::Edges`] to serialize/deserialize.
|
||||
/// [`gpui::Edges`] with serde support.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Edges<T: Clone + Debug + Default + PartialEq> {
|
||||
/// The size of the top edge.
|
||||
pub top: T,
|
||||
/// The size of the right edge.
|
||||
pub right: T,
|
||||
/// The size of the bottom edge.
|
||||
pub bottom: T,
|
||||
/// The size of the left edge.
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
+4
-27
@@ -1,8 +1,8 @@
|
||||
use gpui::{App, Pixels, px};
|
||||
|
||||
pub mod builtins;
|
||||
mod builtins;
|
||||
mod geometry;
|
||||
pub mod model;
|
||||
mod model;
|
||||
mod notification;
|
||||
mod platform_kind;
|
||||
mod resolved;
|
||||
@@ -17,26 +17,15 @@ pub use resolved::{ResolvedAccentRoles, ResolvedColors};
|
||||
pub use runtime::Theme;
|
||||
pub use scrollbar_mode::*;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Defines window border size for platforms that use client side decorations.
|
||||
pub const CLIENT_SIDE_DECORATION_BORDER: Pixels = px(1.0);
|
||||
|
||||
/// Defines window titlebar height
|
||||
pub(crate) const TITLEBAR_HEIGHT: Pixels = px(36.0);
|
||||
|
||||
/// Defines workspace tabbar height
|
||||
pub const TABBAR_HEIGHT: Pixels = px(36.0);
|
||||
|
||||
/// Defines default sidebar width
|
||||
pub const SIDEBAR_WIDTH: Pixels = px(240.);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
for issue in ThemeRegistry::active().validate() {
|
||||
for issue in ThemeRegistry::builtin().validate() {
|
||||
log::warn!("theme {}: {}", issue.variant_id, issue.message);
|
||||
}
|
||||
|
||||
@@ -44,19 +33,7 @@ pub fn init(cx: &mut App) {
|
||||
Theme::sync_scrollbar_appearance(cx);
|
||||
}
|
||||
|
||||
/// Mirror the active coop theme into the `gpui-base` global theme.
|
||||
///
|
||||
/// Base paints a few things from its own tokens -- the focus ring, the wash
|
||||
/// behind selected text, scrollbars, and overlay backdrops -- so the two
|
||||
/// globals have to agree or those details drift away from the palette.
|
||||
///
|
||||
/// Only roles base can act on are projected. Radius, spacing, typography sizes,
|
||||
/// shadows, and scrollbar geometry keep their base defaults: coop has a single
|
||||
/// `radius`/`radius_lg`/`font_size` where base has six-point scales, so any
|
||||
/// mapping would be invented rather than derived.
|
||||
///
|
||||
/// This is a no-op before the coop theme global exists; [`Theme::change`] is the
|
||||
/// authoritative hook that keeps the projection current.
|
||||
/// Project the active theme onto the `gpui-base` global (focus ring, selection wash, scrollbars, backdrops).
|
||||
pub fn sync_base(cx: &mut App) {
|
||||
let Some(theme) = cx.try_global::<Theme>() else {
|
||||
return;
|
||||
|
||||
+14
-96
@@ -1,23 +1,10 @@
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub use crate::builtins::builtin_registry;
|
||||
|
||||
fn custom_families() -> &'static RwLock<Vec<ThemeFamily>> {
|
||||
static CUSTOM: OnceLock<RwLock<Vec<ThemeFamily>>> = OnceLock::new();
|
||||
CUSTOM.get_or_init(|| RwLock::new(Vec::new()))
|
||||
}
|
||||
|
||||
/// Replace the process-wide custom portion of the runtime registry.
|
||||
pub fn replace_custom_families(families: Vec<ThemeFamily>) {
|
||||
*custom_families()
|
||||
.write()
|
||||
.expect("custom theme registry lock was poisoned") = families;
|
||||
}
|
||||
use crate::builtins::builtin_registry;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -28,8 +15,6 @@ pub enum Appearance {
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
pub const ALL: [Self; 2] = [Self::Light, Self::Dark];
|
||||
|
||||
pub fn is_dark(self) -> bool {
|
||||
matches!(self, Self::Dark)
|
||||
}
|
||||
@@ -53,8 +38,6 @@ pub enum SurfacePreference {
|
||||
}
|
||||
|
||||
impl SurfacePreference {
|
||||
pub const ALL: [Self; 3] = [Self::ThemeDefault, Self::Frosted, Self::Opaque];
|
||||
|
||||
pub fn resolve(self, recommended: SurfaceTreatment) -> SurfaceTreatment {
|
||||
match self {
|
||||
Self::ThemeDefault => recommended,
|
||||
@@ -455,7 +438,6 @@ pub struct ThemeVariant {
|
||||
pub family_id: String,
|
||||
pub name: String,
|
||||
pub appearance: Appearance,
|
||||
/// The author's recommended treatment.
|
||||
#[serde(alias = "surfaceTreatment")]
|
||||
pub recommended_surface_treatment: SurfaceTreatment,
|
||||
pub colors: ThemeColors,
|
||||
@@ -486,7 +468,7 @@ pub struct ThemeFamily {
|
||||
pub variants: Vec<ThemeVariant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ThemeRegistry {
|
||||
pub families: Vec<ThemeFamily>,
|
||||
}
|
||||
@@ -496,19 +478,6 @@ impl ThemeRegistry {
|
||||
builtin_registry()
|
||||
}
|
||||
|
||||
/// Built-ins plus the currently installed custom families.
|
||||
pub fn active() -> Self {
|
||||
let mut families = Self::builtin().families.clone();
|
||||
families.extend(
|
||||
custom_families()
|
||||
.read()
|
||||
.expect("custom theme registry lock was poisoned")
|
||||
.iter()
|
||||
.cloned(),
|
||||
);
|
||||
Self { families }
|
||||
}
|
||||
|
||||
pub fn variant(&self, id: &str) -> Option<&ThemeVariant> {
|
||||
self.families
|
||||
.iter()
|
||||
@@ -516,13 +485,6 @@ impl ThemeRegistry {
|
||||
.find(|variant| variant.id == id)
|
||||
}
|
||||
|
||||
pub fn variants_for(&self, appearance: Appearance) -> impl Iterator<Item = &ThemeVariant> {
|
||||
self.families
|
||||
.iter()
|
||||
.flat_map(|family| &family.variants)
|
||||
.filter(move |variant| variant.appearance == appearance)
|
||||
}
|
||||
|
||||
pub fn resolve(&self, selection: &ThemeSelection, appearance: Appearance) -> &ThemeVariant {
|
||||
self.variant(selection.variant_id(appearance))
|
||||
.or_else(|| {
|
||||
@@ -631,25 +593,21 @@ fn validate_contrast(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ValidationSeverity {
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ValidationCategory {
|
||||
Structural,
|
||||
#[default]
|
||||
Contrast,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValidationIssue {
|
||||
pub variant_id: String,
|
||||
#[serde(default)]
|
||||
pub category: ValidationCategory,
|
||||
pub severity: ValidationSeverity,
|
||||
pub message: String,
|
||||
@@ -682,42 +640,6 @@ impl ValidationIssue {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_blocking(&self) -> bool {
|
||||
self.category == ValidationCategory::Structural
|
||||
&& self.severity == ValidationSeverity::Error
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable visual QA scenes every bundled variant must be reviewed against.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum VisualFixture {
|
||||
Sidebar,
|
||||
TranscriptMarkdown,
|
||||
TranscriptCode,
|
||||
Composer,
|
||||
PickerPopover,
|
||||
AppearanceSettings,
|
||||
Diff,
|
||||
Terminal,
|
||||
EmptyState,
|
||||
Dialog,
|
||||
}
|
||||
|
||||
impl VisualFixture {
|
||||
pub const ALL: [Self; 10] = [
|
||||
Self::Sidebar,
|
||||
Self::TranscriptMarkdown,
|
||||
Self::TranscriptCode,
|
||||
Self::Composer,
|
||||
Self::PickerPopover,
|
||||
Self::AppearanceSettings,
|
||||
Self::Diff,
|
||||
Self::Terminal,
|
||||
Self::EmptyState,
|
||||
Self::Dialog,
|
||||
];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -735,7 +657,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn accent_overlay_meets_interaction_contrast() {
|
||||
for appearance in Appearance::ALL {
|
||||
for appearance in [Appearance::Light, Appearance::Dark] {
|
||||
let background = if appearance.is_dark() {
|
||||
Color::rgb(6, 6, 6)
|
||||
} else {
|
||||
@@ -781,6 +703,14 @@ mod tests {
|
||||
fn builtins_have_complete_provenance_and_no_validation_errors() {
|
||||
let registry = ThemeRegistry::builtin();
|
||||
assert_eq!(registry.families.len(), 19);
|
||||
assert_eq!(
|
||||
registry
|
||||
.families
|
||||
.iter()
|
||||
.map(|family| family.variants.len())
|
||||
.sum::<usize>(),
|
||||
30
|
||||
);
|
||||
assert!(registry.variant("zeron-light").is_some());
|
||||
assert!(registry.variant("zeron-dark").is_some());
|
||||
let errors: Vec<_> = registry
|
||||
@@ -790,16 +720,4 @@ mod tests {
|
||||
.collect();
|
||||
assert!(errors.is_empty(), "{errors:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_fixture_matrix_covers_every_variant_and_scene() {
|
||||
let registry = ThemeRegistry::builtin();
|
||||
let variants = registry
|
||||
.families
|
||||
.iter()
|
||||
.map(|family| family.variants.len())
|
||||
.sum::<usize>();
|
||||
assert_eq!(variants, 30);
|
||||
assert_eq!(variants * VisualFixture::ALL.len(), 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,10 @@ use gpui::{Anchor, Pixels, px};
|
||||
|
||||
use crate::{Edges, TITLEBAR_HEIGHT};
|
||||
|
||||
/// The settings for notifications.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NotificationSettings {
|
||||
/// The placement of the notification, default: [`Anchor::TopRight`]
|
||||
pub placement: Anchor,
|
||||
/// The margins of the notification with respect to the window edges.
|
||||
pub margins: Edges<Pixels>,
|
||||
/// The maximum number of notifications to show at once, default: 10
|
||||
pub max_items: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use gpui::Hsla;
|
||||
|
||||
use crate::model::{AccentRoles, ThemeColors, ThemeVariant};
|
||||
use crate::model::{AccentRoles, Color, ThemeColors, ThemeVariant};
|
||||
|
||||
fn hsla(color: crate::model::Color) -> Hsla {
|
||||
fn hsla(color: Color) -> Hsla {
|
||||
let packed = ((color.r as u32) << 24)
|
||||
| ((color.g as u32) << 16)
|
||||
| ((color.b as u32) << 8)
|
||||
@@ -10,23 +10,17 @@ fn hsla(color: crate::model::Color) -> Hsla {
|
||||
Hsla::from(gpui::rgba(packed))
|
||||
}
|
||||
|
||||
/// The accent roles a component can consume, pre-resolved to `Hsla` together
|
||||
/// with the derived states (hover/active, contrast-safe text) that several
|
||||
/// components need; computing them here keeps call sites free of color math.
|
||||
/// Accent roles resolved to [`Hsla`], with derived interaction states precomputed.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ResolvedAccentRoles {
|
||||
pub primary: Hsla,
|
||||
pub strong: Hsla,
|
||||
pub wash: Hsla,
|
||||
pub on: Hsla,
|
||||
pub selection: Hsla,
|
||||
pub caret: Hsla,
|
||||
pub activity: Hsla,
|
||||
pub glyph: [Hsla; 3],
|
||||
pub hover: Hsla,
|
||||
pub active: Hsla,
|
||||
pub selected: Hsla,
|
||||
/// Accent text guaranteed readable on `wash`.
|
||||
/// Text guaranteed 4.5:1 readable on `wash`.
|
||||
pub text_on_wash: Hsla,
|
||||
/// Text guaranteed readable on the flat `danger` color.
|
||||
pub on_danger: Hsla,
|
||||
@@ -38,17 +32,9 @@ impl ResolvedAccentRoles {
|
||||
let wash_background = accent.wash.blend_over(background);
|
||||
Self {
|
||||
primary: hsla(accent.primary),
|
||||
strong: hsla(accent.strong),
|
||||
wash: hsla(accent.wash),
|
||||
on: hsla(accent.on),
|
||||
selection: hsla(accent.selection),
|
||||
caret: hsla(accent.caret),
|
||||
activity: hsla(accent.activity),
|
||||
glyph: [
|
||||
hsla(accent.glyph[0]),
|
||||
hsla(accent.glyph[1]),
|
||||
hsla(accent.glyph[2]),
|
||||
],
|
||||
hover: hsla(accent.primary.mix(background, 0.12)),
|
||||
active: hsla(accent.primary.mix(background, 0.2)),
|
||||
selected: hsla(accent.primary.mix(background, 0.28)),
|
||||
@@ -58,8 +44,7 @@ impl ResolvedAccentRoles {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Hsla` snapshot components render from, rebuilt whenever the active
|
||||
/// variant, accent, or appearance changes.
|
||||
/// The `Hsla` snapshot components render from, rebuilt on every theme change.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ResolvedColors {
|
||||
pub background: Hsla,
|
||||
@@ -92,63 +77,34 @@ pub struct ResolvedColors {
|
||||
|
||||
impl ResolvedColors {
|
||||
pub fn resolve(variant: &ThemeVariant) -> Self {
|
||||
let colors = variant.colors.clone();
|
||||
let ThemeColors {
|
||||
background,
|
||||
shell,
|
||||
raised,
|
||||
card,
|
||||
dialog,
|
||||
overlay,
|
||||
hover,
|
||||
active,
|
||||
border,
|
||||
border_strong,
|
||||
text,
|
||||
text_muted,
|
||||
text_faint,
|
||||
solid,
|
||||
on_solid,
|
||||
danger,
|
||||
danger_muted,
|
||||
warning,
|
||||
warning_muted,
|
||||
success,
|
||||
success_muted,
|
||||
input,
|
||||
cursor,
|
||||
diff_add,
|
||||
diff_delete,
|
||||
diff_hunk,
|
||||
} = colors;
|
||||
|
||||
let colors = &variant.colors;
|
||||
Self {
|
||||
background: hsla(background),
|
||||
shell: hsla(shell),
|
||||
raised: hsla(raised),
|
||||
card: hsla(card),
|
||||
dialog: hsla(dialog),
|
||||
overlay: hsla(overlay),
|
||||
hover: hsla(hover),
|
||||
active: hsla(active),
|
||||
border: hsla(border),
|
||||
border_strong: hsla(border_strong),
|
||||
text: hsla(text),
|
||||
text_muted: hsla(text_muted),
|
||||
text_faint: hsla(text_faint),
|
||||
solid: hsla(solid),
|
||||
on_solid: hsla(on_solid),
|
||||
danger: hsla(danger),
|
||||
danger_muted: hsla(danger_muted),
|
||||
warning: hsla(warning),
|
||||
warning_muted: hsla(warning_muted),
|
||||
success: hsla(success),
|
||||
success_muted: hsla(success_muted),
|
||||
input: hsla(input),
|
||||
cursor: hsla(cursor),
|
||||
diff_add: hsla(diff_add),
|
||||
diff_delete: hsla(diff_delete),
|
||||
diff_hunk: hsla(diff_hunk),
|
||||
background: hsla(colors.background),
|
||||
shell: hsla(colors.shell),
|
||||
raised: hsla(colors.raised),
|
||||
card: hsla(colors.card),
|
||||
dialog: hsla(colors.dialog),
|
||||
overlay: hsla(colors.overlay),
|
||||
hover: hsla(colors.hover),
|
||||
active: hsla(colors.active),
|
||||
border: hsla(colors.border),
|
||||
border_strong: hsla(colors.border_strong),
|
||||
text: hsla(colors.text),
|
||||
text_muted: hsla(colors.text_muted),
|
||||
text_faint: hsla(colors.text_faint),
|
||||
solid: hsla(colors.solid),
|
||||
on_solid: hsla(colors.on_solid),
|
||||
danger: hsla(colors.danger),
|
||||
danger_muted: hsla(colors.danger_muted),
|
||||
warning: hsla(colors.warning),
|
||||
warning_muted: hsla(colors.warning_muted),
|
||||
success: hsla(colors.success),
|
||||
success_muted: hsla(colors.success_muted),
|
||||
input: hsla(colors.input),
|
||||
cursor: hsla(colors.cursor),
|
||||
diff_add: hsla(colors.diff_add),
|
||||
diff_delete: hsla(colors.diff_delete),
|
||||
diff_hunk: hsla(colors.diff_hunk),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-65
@@ -1,4 +1,4 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{App, Global, Pixels, SharedString, Window, px};
|
||||
@@ -21,49 +21,21 @@ impl From<gpui::WindowAppearance> for Appearance {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Theme {
|
||||
/// The active theme variant.
|
||||
pub variant: Arc<ThemeVariant>,
|
||||
|
||||
/// The active appearance (light or dark).
|
||||
pub appearance: Appearance,
|
||||
|
||||
/// The per-appearance variant selection.
|
||||
/// Named `theme_selection` to avoid shadowing the `selection` color role via `Deref`.
|
||||
pub theme_selection: ThemeSelection,
|
||||
|
||||
/// The accent override applied on top of the variant.
|
||||
pub accent_selection: AccentSelection,
|
||||
|
||||
/// The user's surface treatment preference.
|
||||
pub surface_preference: SurfacePreference,
|
||||
|
||||
/// The surface treatment in effect for this session.
|
||||
pub surface: SurfaceTreatment,
|
||||
|
||||
/// The resolved accent roles.
|
||||
pub accent: ResolvedAccentRoles,
|
||||
|
||||
/// The resolved color snapshot.
|
||||
pub colors: ResolvedColors,
|
||||
|
||||
/// The font family for the application.
|
||||
pub font_family: SharedString,
|
||||
|
||||
/// The root font size for the application, default is 15px.
|
||||
pub font_size: Pixels,
|
||||
|
||||
/// Radius for the general elements.
|
||||
pub radius: Pixels,
|
||||
|
||||
/// Radius for the large elements, e.g.: dialog, notification.
|
||||
pub radius_lg: Pixels,
|
||||
|
||||
/// Enable shadow for the general elements. default is true
|
||||
pub shadow: bool,
|
||||
|
||||
/// Show the scrollbar mode, default: scrolling
|
||||
pub scrollbar_mode: ScrollbarMode,
|
||||
|
||||
/// Notification settings
|
||||
pub notification: NotificationSettings,
|
||||
}
|
||||
|
||||
@@ -75,43 +47,34 @@ impl Deref for Theme {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Theme {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.colors
|
||||
}
|
||||
}
|
||||
|
||||
impl Global for Theme {}
|
||||
|
||||
impl Theme {
|
||||
/// Returns the global theme reference
|
||||
pub fn global(cx: &App) -> &Theme {
|
||||
cx.global::<Theme>()
|
||||
}
|
||||
|
||||
/// Returns the global theme mutable reference
|
||||
pub(crate) fn global_mut(cx: &mut App) -> &mut Theme {
|
||||
cx.global_mut::<Theme>()
|
||||
}
|
||||
|
||||
/// Returns true if the theme is dark.
|
||||
pub fn is_dark(&self) -> bool {
|
||||
self.appearance.is_dark()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
let platform = PlatformKind::platform();
|
||||
let appearance = Appearance::default();
|
||||
let selection = ThemeSelection::default();
|
||||
|
||||
// Define the font family based on the platform.
|
||||
let font_family = match platform {
|
||||
let font_family = match PlatformKind::platform() {
|
||||
PlatformKind::Linux => "Inter",
|
||||
_ => ".SystemUIFont",
|
||||
};
|
||||
|
||||
let selection = ThemeSelection::default();
|
||||
let registry = ThemeRegistry::active();
|
||||
let variant = registry.resolve(&selection, appearance).clone();
|
||||
let variant = ThemeRegistry::builtin()
|
||||
.resolve(&selection, appearance)
|
||||
.clone();
|
||||
|
||||
let accent = variant.accent_for(AccentSelection::default());
|
||||
let surface = SurfacePreference::default().resolve(variant.recommended_surface_treatment);
|
||||
let resolved_accent = ResolvedAccentRoles::resolve(&accent, &variant.colors);
|
||||
@@ -136,7 +99,6 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync the theme with the system appearance
|
||||
pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
|
||||
let appearance = window
|
||||
.as_ref()
|
||||
@@ -146,7 +108,6 @@ impl Theme {
|
||||
Self::change(appearance, window, cx);
|
||||
}
|
||||
|
||||
/// Sync the Scrollbar showing behavior with the system
|
||||
pub fn sync_scrollbar_appearance(cx: &mut App) {
|
||||
Theme::global_mut(cx).scrollbar_mode = if cx.should_auto_hide_scrollbars() {
|
||||
ScrollbarMode::Scrolling
|
||||
@@ -155,32 +116,19 @@ impl Theme {
|
||||
};
|
||||
}
|
||||
|
||||
/// Apply a theme variant by id for the current appearance.
|
||||
pub fn apply_variant(id: &str, window: Option<&mut Window>, cx: &mut App) {
|
||||
let appearance = Theme::global(cx).appearance;
|
||||
Theme::global_mut(cx)
|
||||
.theme_selection
|
||||
.set_variant(appearance, id);
|
||||
Self::change(appearance, window, cx);
|
||||
}
|
||||
|
||||
/// Replace the per-appearance variant selection.
|
||||
pub fn set_selection(selection: ThemeSelection, cx: &mut App) {
|
||||
Theme::global_mut(cx).theme_selection = selection;
|
||||
}
|
||||
|
||||
/// Replace the accent override and re-resolve the theme.
|
||||
pub fn set_accent_selection(selection: AccentSelection, cx: &mut App) {
|
||||
Theme::global_mut(cx).accent_selection = selection;
|
||||
}
|
||||
|
||||
/// Replace the surface treatment preference.
|
||||
pub fn set_surface_preference(preference: SurfacePreference, cx: &mut App) {
|
||||
Theme::global_mut(cx).surface_preference = preference;
|
||||
}
|
||||
|
||||
/// Change the app's appearance, resolving the selected variant (with its
|
||||
/// accent override) into a fresh color snapshot.
|
||||
/// Switch appearance and re-resolve the selected variant, accent, and surface.
|
||||
pub fn change<M>(appearance: M, window: Option<&mut Window>, cx: &mut App)
|
||||
where
|
||||
M: Into<Appearance>,
|
||||
@@ -190,6 +138,7 @@ impl Theme {
|
||||
}
|
||||
|
||||
let appearance = appearance.into();
|
||||
|
||||
let (selection, accent_selection, surface_preference) = {
|
||||
let theme = cx.global_mut::<Theme>();
|
||||
theme.appearance = appearance;
|
||||
@@ -200,8 +149,10 @@ impl Theme {
|
||||
)
|
||||
};
|
||||
|
||||
let registry = ThemeRegistry::active();
|
||||
let variant = registry.resolve(&selection, appearance).clone();
|
||||
let variant = ThemeRegistry::builtin()
|
||||
.resolve(&selection, appearance)
|
||||
.clone();
|
||||
|
||||
let accent = variant.accent_for(accent_selection);
|
||||
let surface = surface_preference.resolve(variant.recommended_surface_treatment);
|
||||
let colors = ResolvedColors::resolve(&variant);
|
||||
@@ -214,12 +165,10 @@ impl Theme {
|
||||
theme.colors = colors;
|
||||
}
|
||||
|
||||
// Refresh the window if available
|
||||
if let Some(window) = window {
|
||||
window.refresh();
|
||||
}
|
||||
|
||||
// Keep the base-layer projection in step with the coop palette
|
||||
sync_base(cx);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user