use std::ops::{Deref, DerefMut}; use std::sync::Arc; use gpui::{App, Global, Pixels, SharedString, Window, px}; use crate::model::{ AccentSelection, Appearance, SurfacePreference, SurfaceTreatment, ThemeRegistry, ThemeSelection, ThemeVariant, }; use crate::resolved::{ResolvedAccentRoles, ResolvedColors}; use crate::{NotificationSettings, PlatformKind, ScrollbarMode, sync_base}; impl From for Appearance { fn from(appearance: gpui::WindowAppearance) -> Self { match appearance { gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => Self::Dark, gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => Self::Light, } } } #[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. 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, } impl Deref for Theme { type Target = ResolvedColors; fn deref(&self) -> &Self::Target { &self.colors } } 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(); // Define the font family based on the platform. let font_family = match platform { PlatformKind::Linux => "Inter", _ => ".SystemUIFont", }; let selection = ThemeSelection::default(); let registry = ThemeRegistry::active(); let variant = registry.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); let colors = ResolvedColors::resolve(&variant, &accent); Self { variant: Arc::new(variant), appearance, theme_selection: selection, accent_selection: AccentSelection::default(), surface_preference: SurfacePreference::default(), surface, accent: resolved_accent, colors, font_family: font_family.into(), font_size: px(15.), radius: px(6.), radius_lg: px(10.), shadow: true, scrollbar_mode: ScrollbarMode::default(), notification: NotificationSettings::default(), } } /// Sync the theme with the system appearance pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) { let appearance = window .as_ref() .map(|window| window.appearance()) .unwrap_or_else(|| cx.window_appearance()); 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 } else { ScrollbarMode::Hover }; } /// 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. pub fn change(appearance: M, window: Option<&mut Window>, cx: &mut App) where M: Into, { if !cx.has_global::() { cx.set_global(Theme::new()); } let appearance = appearance.into(); let (selection, accent_selection, surface_preference) = { let theme = cx.global_mut::(); theme.appearance = appearance; ( theme.theme_selection.clone(), theme.accent_selection, theme.surface_preference, ) }; let registry = ThemeRegistry::active(); let variant = registry.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, &accent); { let theme = cx.global_mut::(); theme.variant = Arc::new(variant.clone()); theme.accent = ResolvedAccentRoles::resolve(&accent, &variant.colors); theme.surface = surface; 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); } }