Detailed changes
@@ -5,7 +5,7 @@ use futures::StreamExt;
use gpui::{prelude::*, AppContext, WindowHandle};
use settings::Settings;
use std::sync::{Arc, Weak};
-use theme::ThemeSettings;
+use theme::{SystemAppearance, ThemeSettings};
use ui::{prelude::*, Button, Label};
use util::ResultExt;
use workspace::AppState;
@@ -35,6 +35,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
let options = notification_window_options(screen, window_size);
let window = cx
.open_window(options, |cx| {
+ SystemAppearance::init_for_window(cx);
+
cx.new_view(|_| {
IncomingCallNotification::new(
incoming_call.clone(),
@@ -6,7 +6,7 @@ use collections::HashMap;
use gpui::{AppContext, Size};
use settings::Settings;
use std::sync::{Arc, Weak};
-use theme::ThemeSettings;
+use theme::{SystemAppearance, ThemeSettings};
use ui::{prelude::*, Button, Label};
use workspace::AppState;
@@ -28,6 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.displays() {
let options = notification_window_options(screen, window_size);
let window = cx.open_window(options, |cx| {
+ SystemAppearance::init_for_window(cx);
+
cx.new_view(|_| {
ProjectSharedNotification::new(
owner.clone(),
@@ -1,9 +1,10 @@
use crate::one_themes::one_dark;
-use crate::{SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
+use crate::{Appearance, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
use anyhow::Result;
+use derive_more::{Deref, DerefMut};
use gpui::{
px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Global, Pixels, Subscription,
- ViewContext,
+ ViewContext, WindowContext,
};
use refineable::Refineable;
use schemars::{
@@ -27,16 +28,104 @@ pub struct ThemeSettings {
pub buffer_font: Font,
pub buffer_font_size: Pixels,
pub buffer_line_height: BufferLineHeight,
- pub requested_theme: Option<String>,
+ pub theme_selection: Option<ThemeSelection>,
pub active_theme: Arc<Theme>,
pub theme_overrides: Option<ThemeStyleContent>,
}
+/// The appearance of the system.
+#[derive(Debug, Clone, Copy, Deref)]
+pub struct SystemAppearance(pub Appearance);
+
+impl Default for SystemAppearance {
+ fn default() -> Self {
+ Self(Appearance::Dark)
+ }
+}
+
+#[derive(Deref, DerefMut, Default)]
+struct GlobalSystemAppearance(SystemAppearance);
+
+impl Global for GlobalSystemAppearance {}
+
+impl SystemAppearance {
+ /// Returns the global [`SystemAppearance`].
+ ///
+ /// Inserts a default [`SystemAppearance`] if one does not yet exist.
+ pub(crate) fn default_global(cx: &mut AppContext) -> Self {
+ cx.default_global::<GlobalSystemAppearance>().0
+ }
+
+ /// Initializes the [`SystemAppearance`] for the current window.
+ pub fn init_for_window(cx: &mut WindowContext) {
+ *cx.default_global::<GlobalSystemAppearance>() =
+ GlobalSystemAppearance(SystemAppearance(cx.appearance().into()));
+ }
+
+ /// Returns the global [`SystemAppearance`].
+ pub fn global(cx: &AppContext) -> Self {
+ cx.global::<GlobalSystemAppearance>().0
+ }
+
+ /// Returns a mutable reference to the global [`SystemAppearance`].
+ pub fn global_mut(cx: &mut AppContext) -> &mut Self {
+ cx.global_mut::<GlobalSystemAppearance>()
+ }
+}
+
#[derive(Default)]
pub(crate) struct AdjustedBufferFontSize(Pixels);
impl Global for AdjustedBufferFontSize {}
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+#[serde(untagged)]
+pub enum ThemeSelection {
+ Static(#[schemars(schema_with = "theme_name_ref")] String),
+ Dynamic {
+ #[serde(default)]
+ mode: ThemeMode,
+ #[schemars(schema_with = "theme_name_ref")]
+ light: String,
+ #[schemars(schema_with = "theme_name_ref")]
+ dark: String,
+ },
+}
+
+fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
+ Schema::new_ref("#/definitions/ThemeName".into())
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ThemeMode {
+ /// Use the specified `light` theme.
+ Light,
+
+ /// Use the specified `dark` theme.
+ Dark,
+
+ /// Use the theme based on the system's appearance.
+ #[default]
+ System,
+}
+
+impl ThemeSelection {
+ pub fn theme(&self, system_appearance: Appearance) -> &str {
+ match self {
+ Self::Static(theme) => theme,
+ Self::Dynamic { mode, light, dark } => match mode {
+ ThemeMode::Light => light,
+ ThemeMode::Dark => dark,
+ ThemeMode::System => match system_appearance {
+ Appearance::Light => light,
+ Appearance::Dark => dark,
+ },
+ },
+ }
+ }
+}
+
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct ThemeSettingsContent {
#[serde(default)]
@@ -54,7 +143,7 @@ pub struct ThemeSettingsContent {
#[serde(default)]
pub buffer_font_features: Option<FontFeatures>,
#[serde(default)]
- pub theme: Option<String>,
+ pub theme: Option<ThemeSelection>,
/// EXPERIMENTAL: Overrides for the current theme.
///
@@ -188,6 +277,7 @@ impl settings::Settings for ThemeSettings {
cx: &mut AppContext,
) -> Result<Self> {
let themes = ThemeRegistry::default_global(cx);
+ let system_appearance = SystemAppearance::default_global(cx);
let mut this = Self {
ui_font_size: defaults.ui_font_size.unwrap().into(),
@@ -205,9 +295,9 @@ impl settings::Settings for ThemeSettings {
},
buffer_font_size: defaults.buffer_font_size.unwrap().into(),
buffer_line_height: defaults.buffer_line_height.unwrap(),
- requested_theme: defaults.theme.clone(),
+ theme_selection: defaults.theme.clone(),
active_theme: themes
- .get(defaults.theme.as_ref().unwrap())
+ .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
.or(themes.get(&one_dark().name))
.unwrap(),
theme_overrides: None,
@@ -229,9 +319,11 @@ impl settings::Settings for ThemeSettings {
}
if let Some(value) = &value.theme {
- this.requested_theme = Some(value.clone());
+ this.theme_selection = Some(value.clone());
+
+ let theme_name = value.theme(*system_appearance);
- if let Some(theme) = themes.get(value).log_err() {
+ if let Some(theme) = themes.get(theme_name).log_err() {
this.active_theme = theme;
}
}
@@ -291,10 +383,6 @@ impl settings::Settings for ThemeSettings {
.unwrap()
.properties
.extend([
- (
- "theme".to_owned(),
- Schema::new_ref("#/definitions/ThemeName".into()),
- ),
(
"buffer_font_family".to_owned(),
Schema::new_ref("#/definitions/FontFamilies".into()),
@@ -27,7 +27,7 @@ pub use schema::*;
pub use settings::*;
pub use styles::*;
-use gpui::{AppContext, AssetSource, Hsla, SharedString};
+use gpui::{AppContext, AssetSource, Hsla, SharedString, WindowAppearance};
use serde::Deserialize;
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
@@ -45,6 +45,15 @@ impl Appearance {
}
}
+impl From<WindowAppearance> for Appearance {
+ fn from(value: WindowAppearance) -> Self {
+ match value {
+ WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
+ WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
+ }
+ }
+}
+
pub enum LoadThemes {
/// Only load the base theme.
///
@@ -9,7 +9,9 @@ use gpui::{
use picker::{Picker, PickerDelegate};
use settings::{update_settings_file, SettingsStore};
use std::sync::Arc;
-use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
+use theme::{
+ Appearance, Theme, ThemeMeta, ThemeMode, ThemeRegistry, ThemeSelection, ThemeSettings,
+};
use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, ModalView, Workspace};
@@ -167,8 +169,26 @@ impl PickerDelegate for ThemeSelectorDelegate {
self.telemetry
.report_setting_event("theme", theme_name.to_string());
+ let appearance = Appearance::from(cx.appearance());
+
update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
- settings.theme = Some(theme_name.to_string());
+ if let Some(selection) = settings.theme.as_mut() {
+ let theme_to_update = match selection {
+ ThemeSelection::Static(theme) => theme,
+ ThemeSelection::Dynamic { mode, light, dark } => match mode {
+ ThemeMode::Light => light,
+ ThemeMode::Dark => dark,
+ ThemeMode::System => match appearance {
+ Appearance::Light => light,
+ Appearance::Dark => dark,
+ },
+ },
+ };
+
+ *theme_to_update = theme_name.to_string();
+ } else {
+ settings.theme = Some(ThemeSelection::Static(theme_name.to_string()));
+ }
});
self.view
@@ -64,7 +64,7 @@ use std::{
sync::{atomic::AtomicUsize, Arc},
time::Duration,
};
-use theme::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use ui::Label;
@@ -682,6 +682,21 @@ impl Workspace {
}
cx.notify();
}),
+ cx.observe_window_appearance(|_, cx| {
+ let window_appearance = cx.appearance();
+
+ *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
+
+ let mut theme_settings = ThemeSettings::get_global(cx).clone();
+
+ if let Some(theme_selection) = theme_settings.theme_selection.clone() {
+ let theme_name = theme_selection.theme(window_appearance.into());
+
+ if let Some(_theme) = theme_settings.switch_theme(&theme_name, cx) {
+ ThemeSettings::override_global(theme_settings, cx);
+ }
+ }
+ }),
cx.observe(&left_dock, |this, _, cx| {
this.serialize_workspace(cx);
cx.notify();
@@ -840,6 +855,8 @@ impl Workspace {
let workspace_id = workspace_id.clone();
let project_handle = project_handle.clone();
move |cx| {
+ SystemAppearance::init_for_window(cx);
+
cx.new_view(|cx| {
Workspace::new(workspace_id, project_handle, app_state, cx)
})
@@ -43,7 +43,7 @@ use std::{
thread,
time::Duration,
};
-use theme::{ActiveTheme, ThemeRegistry, ThemeSettings};
+use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
use util::{
async_maybe,
http::{self, HttpClient, ZedHttpClient},
@@ -912,8 +912,10 @@ fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
theme_registry.load_user_themes(themes_dir, fs).await?;
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
- if let Some(requested_theme) = theme_settings.requested_theme.clone() {
- if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx) {
+ if let Some(theme_selection) = theme_settings.theme_selection.clone() {
+ let theme_name = theme_selection.theme(*SystemAppearance::global(cx));
+
+ if let Some(_theme) = theme_settings.switch_theme(&theme_name, cx) {
ThemeSettings::override_global(theme_settings, cx);
}
}
@@ -949,11 +951,14 @@ fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
- if let Some(requested_theme) =
- theme_settings.requested_theme.clone()
+ if let Some(theme_selection) =
+ theme_settings.theme_selection.clone()
{
+ let theme_name =
+ theme_selection.theme(*SystemAppearance::global(cx));
+
if let Some(_theme) =
- theme_settings.switch_theme(&requested_theme, cx)
+ theme_settings.switch_theme(&theme_name, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}