From 4dfc9fd6682e914d97cac00ab0ad2c2799775fe5 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 8 Oct 2026 09:25:24 +0700 Subject: [PATCH] simplify theme crate to builtin-only registry --- Cargo.lock | 1 - crates/theme/Cargo.toml | 1 - crates/theme/src/builtins.rs | 33 +++++----- crates/theme/src/geometry.rs | 12 +--- crates/theme/src/lib.rs | 31 ++------- crates/theme/src/model.rs | 110 ++++--------------------------- crates/theme/src/notification.rs | 4 -- crates/theme/src/resolved.rs | 108 +++++++++--------------------- crates/theme/src/runtime.rs | 79 ++++------------------ crates/workspace/src/lib.rs | 2 +- 10 files changed, 82 insertions(+), 299 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d0bdee2..82e17c33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7436,7 +7436,6 @@ dependencies = [ name = "theme" version = "1.0.2" dependencies = [ - "anyhow", "gpui-base", "gpui-pre", "log", diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index f0e04b24..a9dbe6d1 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -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 diff --git a/crates/theme/src/builtins.rs b/crates/theme/src/builtins.rs index d33b3e2f..089a4bed 100644 --- a/crates/theme/src/builtins.rs +++ b/crates/theme/src/builtins.rs @@ -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()], + ), ], }) } diff --git a/crates/theme/src/geometry.rs b/crates/theme/src/geometry.rs index 10ed548c..1bc44fb4 100644 --- a/crates/theme/src/geometry.rs +++ b/crates/theme/src/geometry.rs @@ -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 { - /// 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, } diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index f2f62b4b..58da61ac 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -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::() else { return; diff --git a/crates/theme/src/model.rs b/crates/theme/src/model.rs index 6153d6d1..f7a054bb 100644 --- a/crates/theme/src/model.rs +++ b/crates/theme/src/model.rs @@ -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> { - static CUSTOM: OnceLock>> = 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) { - *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, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ThemeRegistry { pub families: Vec, } @@ -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 { - 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::(), + 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::(); - assert_eq!(variants, 30); - assert_eq!(variants * VisualFixture::ALL.len(), 300); - } } diff --git a/crates/theme/src/notification.rs b/crates/theme/src/notification.rs index 85021f1b..e838ab23 100644 --- a/crates/theme/src/notification.rs +++ b/crates/theme/src/notification.rs @@ -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, - /// The maximum number of notifications to show at once, default: 10 pub max_items: usize, } diff --git a/crates/theme/src/resolved.rs b/crates/theme/src/resolved.rs index b8521414..f1667e18 100644 --- a/crates/theme/src/resolved.rs +++ b/crates/theme/src/resolved.rs @@ -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), } } } diff --git a/crates/theme/src/runtime.rs b/crates/theme/src/runtime.rs index 4a597b04..a5cb6f1b 100644 --- a/crates/theme/src/runtime.rs +++ b/crates/theme/src/runtime.rs @@ -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 for Appearance { #[derive(Debug, Clone)] pub struct Theme { - /// The active theme variant. pub variant: Arc, - - /// 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::() } - /// Returns the global theme mutable reference pub(crate) fn global_mut(cx: &mut App) -> &mut Theme { cx.global_mut::() } - /// 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(appearance: M, window: Option<&mut Window>, cx: &mut App) where M: Into, @@ -190,6 +138,7 @@ impl Theme { } let appearance = appearance.into(); + let (selection, accent_selection, surface_preference) = { let theme = cx.global_mut::(); 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); } } diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 1b426b40..d95ea6ae 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -465,7 +465,7 @@ impl Workspace { fn theme_selector(&mut self, window: &mut Window, cx: &mut Context) { let appearance = cx.theme().appearance; - let registry = ThemeRegistry::active(); + let registry = ThemeRegistry::builtin(); let groups: Vec<(String, Vec)> = registry .families .iter()