From 8e8ceaf2db92e674b4d73199f5cdfe743a987f78 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Mon, 9 Nov 2026 21:33:20 +0700 Subject: [PATCH] migrate theming to gpui-native-kit --- Cargo.lock | 268 ++++++++++++++++------- crates/settings/Cargo.toml | 1 - crates/settings/src/lib.rs | 109 +-------- crates/speech/src/lib.rs | 5 + crates/workspace/Cargo.toml | 1 + crates/workspace/src/dialogs/settings.rs | 140 +----------- crates/workspace/src/lib.rs | 38 +++- desktop/Cargo.toml | 6 +- desktop/src/main.rs | 111 ++-------- web/Cargo.toml | 3 +- web/src/lib.rs | 52 ++--- 11 files changed, 278 insertions(+), 456 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6449febc..013f5b9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1707,12 +1707,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "configparser" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031ad3d167622974ed636505ae4556267ed4aa2aea0464122fa6bc9698a32825" - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1811,8 +1805,7 @@ dependencies = [ "auto_update", "chat", "chat_ui", - "flume 0.11.1", - "gpui-kit", + "gpui-native-kit", "gpui-pre", "gpui-pre-linux", "gpui-pre-macos", @@ -1820,14 +1813,13 @@ dependencies = [ "gpui-pre-reqwest-client", "gpui-pre-windows", "log", - "native-theme", - "native-theme-gpui", "nip29", "nip4e", "nip60", "nostr-sdk", "person", "settings", + "speech", "state", "tracing-subscriber", "ui", @@ -1846,7 +1838,7 @@ dependencies = [ "getrandom 0.2.17", "getrandom 0.3.4", "getrandom 0.4.3", - "gpui-kit", + "gpui-native-kit", "gpui-pre", "gpui-pre-platform", "gpui-pre-web", @@ -1857,6 +1849,7 @@ dependencies = [ "nip4e", "person", "settings", + "speech", "state", "tracing-wasm", "ui", @@ -3638,6 +3631,114 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "gpui-native-core" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-icons", + "gpui-native-theme", + "gpui-native-utils", + "gpui-pre", + "unicode-segmentation", +] + +[[package]] +name = "gpui-native-data" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-core", + "gpui-native-icons", + "gpui-native-theme", + "gpui-native-utils", + "gpui-pre", +] + +[[package]] +name = "gpui-native-editor" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-core", + "gpui-native-icons", + "gpui-native-theme", + "gpui-native-utils", + "gpui-pre", + "pulldown-cmark", + "similar", + "tree-sitter", + "tree-sitter-javascript", + "tree-sitter-python", + "tree-sitter-rust", + "tree-sitter-typescript", +] + +[[package]] +name = "gpui-native-icons" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "anyhow", + "gpui-pre", +] + +[[package]] +name = "gpui-native-kit" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-core", + "gpui-native-data", + "gpui-native-editor", + "gpui-native-icons", + "gpui-native-layout", + "gpui-native-theme", + "gpui-pre", +] + +[[package]] +name = "gpui-native-layout" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-core", + "gpui-native-data", + "gpui-native-icons", + "gpui-native-theme", + "gpui-native-utils", + "gpui-pre", +] + +[[package]] +name = "gpui-native-theme" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "anyhow", + "async-channel", + "block2 0.6.2", + "gpui-pre", + "notify", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "serde", + "toml 0.8.23", + "windows 0.62.2", + "windows-registry 0.6.1", + "zbus", +] + +[[package]] +name = "gpui-native-utils" +version = "0.1.0" +source = "git+https://git.reya.info/reya/gpui-native-kit?rev=b75968ee0fecf78c3041248f1f6bfe8bfd85dee0#b75968ee0fecf78c3041248f1f6bfe8bfd85dee0" +dependencies = [ + "gpui-native-theme", + "gpui-pre", +] + [[package]] name = "gpui-pre" version = "0.3.10" @@ -3678,7 +3779,7 @@ dependencies = [ "parking", "parking_lot", "pin-project", - "pollster 0.4.0", + "pollster", "profiling", "rand 0.9.5", "raw-window-handle", @@ -5918,62 +6019,6 @@ dependencies = [ "thiserror 2.0.21", ] -[[package]] -name = "native-theme" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2a6c2d7520b94d53802c0c94d0450280a7c10380246756661bd1c6ffc64dd1" -dependencies = [ - "arc-swap", - "ashpd", - "async-io", - "async-trait", - "block2 0.6.2", - "configparser", - "fontdb", - "inventory", - "native-theme-derive", - "notify", - "objc2 0.6.5", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-core-text", - "objc2-foundation 0.3.2", - "pollster 1.0.1", - "serde", - "serde_with", - "toml 1.1.8+spec-1.1.0", - "unicase", - "windows 0.62.2", - "windows-version", - "zbus", -] - -[[package]] -name = "native-theme-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba9ec586e35be9d05bdd279adc9cbe213b5bd630536b8066b82caaaf4021dba7" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", -] - -[[package]] -name = "native-theme-gpui" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e13cbaa86f1f0a1ac947380f5d8d47a5e9f7f939c77afd05d7039041abae146" -dependencies = [ - "gpui-base", - "gpui-component", - "gpui-kit", - "gpui-pre", - "image", - "native-theme", -] - [[package]] name = "native-tls" version = "0.2.18" @@ -6781,8 +6826,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ "bitflags 2.13.2", - "block2 0.6.2", - "libc", "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", @@ -7587,12 +7630,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" -[[package]] -name = "pollster" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336" - [[package]] name = "poly1305" version = "0.8.0" @@ -7740,6 +7777,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db2be46835f013dd3f4fa7f5f7402c537787b54ebb6fe2b94bbdd7e2669bb7b" +dependencies = [ + "bitflags 2.13.2", + "memchr", + "unicase", +] + [[package]] name = "pulp" version = "0.22.3" @@ -9047,7 +9095,6 @@ version = "2.0.0-alpha3" dependencies = [ "anyhow", "common", - "gpui-kit", "gpui-pre", "log", "nostr-sdk", @@ -9463,6 +9510,12 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -10270,6 +10323,66 @@ dependencies = [ "strength_reduce", ] +[[package]] +name = "tree-sitter" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0af592be68c579aa78a16846bd19422978c3c52e438523d45ff5d1bff1f9d4a" + +[[package]] +name = "tree-sitter-python" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "triomphe" version = "0.1.17" @@ -11957,6 +12070,7 @@ dependencies = [ "common", "gpui-base", "gpui-kit", + "gpui-native-kit", "gpui-pre", "instant", "log", diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 793e6e3b..f68be09d 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true publish.workspace = true [dependencies] -gpui-kit.workspace = true common = { path = "../common" } nostr-sdk.workspace = true diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 714add2c..bc69ae96 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use anyhow::{Error, anyhow}; #[cfg(not(target_arch = "wasm32"))] use common::config_dir; -use gpui::{App, AppContext, Context, Entity, Global, ImageSource, Subscription, Task, px}; +use gpui::{App, AppContext, Context, Entity, Global, ImageSource, Subscription, Task}; use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use smallvec::{SmallVec, smallvec}; @@ -36,33 +36,6 @@ impl AppearanceMode { } } -/// The default base font size for the interface -pub const DEFAULT_FONT_SIZE: f32 = 16.0; - -/// User tuning applied on top of the native OS theme -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct ThemeSettings { - /// The base UI font size in pixels - pub font_size: f32, - /// Draw a ring around focused controls - pub focus_ring: bool, - /// Corner radius for general elements; follows the OS theme when unset - pub radius: Option, - /// Corner radius for large elements like dialogs; follows the OS theme when unset - pub radius_lg: Option, -} - -impl Default for ThemeSettings { - fn default() -> Self { - Self { - font_size: DEFAULT_FONT_SIZE, - focus_ring: false, - radius: None, - radius_lg: None, - } - } -} - pub fn init(cx: &mut App) { AppSettings::set_global(cx.new(AppSettings::new), cx) } @@ -91,13 +64,6 @@ pub fn persisted_appearance_mode() -> AppearanceMode { .unwrap_or_default() } -/// Read the persisted theme tuning synchronously. -pub fn persisted_theme() -> ThemeSettings { - persisted_settings() - .map(|settings| settings.theme) - .unwrap_or_default() -} - const DEFAULT_FILE_SERVER: &str = "https://nostr.download/"; const LEGACY_FILE_SERVER: &str = "blossom.band"; /// Minimum days between automatic encryption key rotations @@ -160,7 +126,6 @@ setting_accessors! { } setting_getters! { - pub theme: ThemeSettings, pub pinned_rooms: Vec, pub pinned_groups: Vec, } @@ -234,9 +199,6 @@ pub struct Settings { /// Hide the notice that messages in public groups are not encrypted #[serde(default)] pub hide_group_notice: bool, - /// Tuning applied on top of the native OS theme - #[serde(default)] - pub theme: ThemeSettings, /// Show an image behind chat and group panels #[serde(default)] pub panel_background: bool, @@ -265,7 +227,6 @@ impl Default for Settings { nip4e: false, nip4e_rotation_days: None, hide_group_notice: false, - theme: ThemeSettings::default(), panel_background: true, panel_background_path: None, trusted_relays: vec![], @@ -330,52 +291,6 @@ impl AppSettings { }); } - /// Edit the theme tuning - pub fn update_theme(edit: impl FnOnce(&mut ThemeSettings), cx: &mut App) { - Self::global(cx).update(cx, |this, cx| { - this.inner.update(cx, |inner, cx| { - edit(&mut inner.theme); - cx.notify(); - }); - }); - } - - pub fn apply_theme_style(cx: &mut App) { - Self::apply_theme_tuning(Self::get_theme(cx), cx); - } - - fn apply_theme_tuning(tuning: ThemeSettings, cx: &mut App) { - use gpui_kit::component::Theme; - - { - let styled = Theme::global_mut(cx); - styled.font_size = px(tuning.font_size); - styled.focus_ring = tuning.focus_ring; - if let Some(radius) = tuning.radius { - styled.radius = px(radius); - } - if let Some(radius_lg) = tuning.radius_lg { - styled.radius_lg = px(radius_lg); - } - } - - Theme::update(cx, |styled| { - let background = styled.background; - let foreground = styled.foreground; - let (hover_alpha, active_alpha) = if styled.is_dark() { - (0.08, 0.12) - } else { - (0.04, 0.06) - }; - if styled.list_hover == background { - styled.list_hover = foreground.alpha(hover_alpha); - } - if styled.tab_active == background { - styled.tab_active = foreground.alpha(active_alpha); - } - }); - } - /// Load settings fn load(&mut self, cx: &mut Context) { let task: Task> = cx.background_spawn(async move { @@ -408,7 +323,6 @@ impl AppSettings { // Update settings this.update(cx, |this, cx| { this.set_settings(settings, cx); - this.apply_theme(cx); cx.refresh_windows(); }) .ok(); @@ -434,20 +348,6 @@ impl AppSettings { } } - /// Apply the persisted appearance mode to the app - pub fn apply_theme(&mut self, cx: &mut Context) { - use gpui_kit::component::Theme; - - let mode = self.inner.read(cx).appearance_mode; - match mode { - AppearanceMode::System => Theme::sync_system_appearance(None, cx), - AppearanceMode::Light => Theme::change(gpui_kit::component::ThemeMode::Light, None, cx), - AppearanceMode::Dark => Theme::change(gpui_kit::component::ThemeMode::Dark, None, cx), - } - let tuning = self.inner.read(cx).theme; - Self::apply_theme_tuning(tuning, cx); - } - /// Check if decoupling encryption key is enabled pub fn is_nip4e_enabled(&self, cx: &App) -> bool { self.inner.read(cx).nip4e @@ -507,9 +407,9 @@ impl AppSettings { mod tests { use super::*; - /// Settings written before theme tuning existed must keep loading. + /// Settings written by older versions, including theme tuning, must keep loading. #[test] - fn settings_without_theme_keep_defaults() { + fn legacy_settings_keep_loading() { let settings: Settings = serde_json::from_str( r#"{ "appearance_mode": "Dark", @@ -517,12 +417,13 @@ mod tests { "screening": true, "wot_auto_trust": true, "nip4e": false, + "theme": { "font_size": 18.0, "focus_ring": true }, "trusted_relays": [], "file_server": "https://nostr.download/" }"#, ) .unwrap(); - assert_eq!(settings.theme, ThemeSettings::default()); + assert_eq!(settings.appearance_mode, AppearanceMode::Dark); } } diff --git a/crates/speech/src/lib.rs b/crates/speech/src/lib.rs index 7db0f630..bf840b65 100644 --- a/crates/speech/src/lib.rs +++ b/crates/speech/src/lib.rs @@ -41,6 +41,11 @@ pub fn recognizer() -> impl SpeechRecognizer { parakeet::ParakeetRecognizer } +/// Initializes the old component kit that the retained speech widgets still use. +pub fn init_compat(cx: &mut App) { + gpui_kit::init(cx); +} + /// The recognizer backing the composer's microphone button. #[cfg(target_arch = "wasm32")] pub fn recognizer() -> impl SpeechRecognizer { diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 088eef75..c86039d4 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -7,6 +7,7 @@ publish.workspace = true [dependencies] ui = { path = "../ui" } gpui-kit.workspace = true +gpui-native-kit.workspace = true common = { path = "../common" } state = { path = "../state" } nip29 = { path = "../nip29" } diff --git a/crates/workspace/src/dialogs/settings.rs b/crates/workspace/src/dialogs/settings.rs index eb3adb5f..d0392084 100644 --- a/crates/workspace/src/dialogs/settings.rs +++ b/crates/workspace/src/dialogs/settings.rs @@ -13,7 +13,7 @@ use gpui::{ }; use gpui_kit::assets::IconName; use gpui_kit::component::button::{Button, ButtonVariants}; -use gpui_kit::component::input::{Input, InputEvent, InputState, NumberInput}; +use gpui_kit::component::input::{Input, InputState}; use gpui_kit::component::kbd::Kbd; use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem}; use gpui_kit::component::notification::Notification; @@ -27,19 +27,12 @@ use settings::{AppSettings, AppearanceMode, NIP4E_ROTATION_DEFAULT_DAYS}; use speech::model::{self as speech_model}; use ui::{settings_block, settings_row}; -/// Bounds for the theme number inputs -const FONT_SIZE_RANGE: (f32, f32) = (10., 32.); -const RADIUS_RANGE: (f32, f32) = (0., 24.); - pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Preferences::new(window, cx)) } pub struct Preferences { file_input: Entity, - font_size_input: Entity, - radius_input: Entity, - radius_lg_input: Entity, #[cfg(not(target_arch = "wasm32"))] mint_input: Entity, #[cfg(not(target_arch = "wasm32"))] @@ -57,61 +50,9 @@ impl Preferences { .placeholder("https://myblossom.com") }); - let theme = AppSettings::get_theme(cx); - let font_size_input = cx.new(|cx| { - InputState::new(window, cx) - .default_value(theme.font_size.to_string()) - .step(1.) - .min(FONT_SIZE_RANGE.0 as f64) - .max(FONT_SIZE_RANGE.1 as f64) - }); - let radius_input = cx.new(|cx| { - InputState::new(window, cx) - .default_value( - theme - .radius - .map(|value| value.to_string()) - .unwrap_or_default(), - ) - .placeholder("System") - .step(1.) - .min(RADIUS_RANGE.0 as f64) - .max(RADIUS_RANGE.1 as f64) - }); - let radius_lg_input = cx.new(|cx| { - InputState::new(window, cx) - .default_value( - theme - .radius_lg - .map(|value| value.to_string()) - .unwrap_or_default(), - ) - .placeholder("System") - .step(1.) - .min(RADIUS_RANGE.0 as f64) - .max(RADIUS_RANGE.1 as f64) - }); - #[cfg(not(target_arch = "wasm32"))] let mint_input = cx.new(|cx| InputState::new(window, cx).placeholder("https://mint…")); - let subscriptions = vec![ - Self::wire_number_input(&font_size_input, FONT_SIZE_RANGE, cx, |value, cx| { - if let Some(font_size) = value { - AppSettings::update_theme(|theme| theme.font_size = font_size, cx); - AppSettings::apply_theme_style(cx); - } - }), - Self::wire_number_input(&radius_input, RADIUS_RANGE, cx, |value, cx| { - AppSettings::update_theme(|theme| theme.radius = value, cx); - AppSettings::apply_theme_style(cx); - }), - Self::wire_number_input(&radius_lg_input, RADIUS_RANGE, cx, |value, cx| { - AppSettings::update_theme(|theme| theme.radius_lg = value, cx); - AppSettings::apply_theme_style(cx); - }), - ]; - #[cfg(not(target_arch = "wasm32"))] let _speech_poll = cx.spawn_in(window, async move |this, cx| { let timer = Duration::from_millis(500); @@ -141,43 +82,14 @@ impl Preferences { Self { file_input, - font_size_input, - radius_input, - radius_lg_input, #[cfg(not(target_arch = "wasm32"))] mint_input, #[cfg(not(target_arch = "wasm32"))] _speech_poll, - _subscriptions: subscriptions, + _subscriptions: Vec::new(), } } - /// Persist a theme number as it changes; an empty value stores `None` - fn wire_number_input( - state: &Entity, - range: (f32, f32), - cx: &mut Context, - persist: impl Fn(Option, &mut App) + 'static, - ) -> Subscription { - let (min, max) = range; - let state = state.clone(); - - cx.subscribe(&state, move |_, state, event: &InputEvent, cx| { - if !matches!(event, InputEvent::Change) { - return; - } - - let value = state - .read(cx) - .value() - .trim() - .parse::() - .ok() - .map(|value| value.clamp(min, max)); - persist(value, cx); - }) - } - /// Update the file server (blossom) URL fn update_file_server(&mut self, window: &mut Window, cx: &mut Context) { let value = self.file_input.read(cx).value(); @@ -245,9 +157,7 @@ impl Preferences { /// Set the appearance mode (system, light or dark) fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) { AppSettings::update_appearance_mode(mode, cx); - AppSettings::global(cx).update(cx, |settings, cx| { - settings.apply_theme(cx); - }); + crate::apply_appearance_mode(mode, cx); } /// Ask the user for an image to show behind chat and group panels @@ -388,11 +298,6 @@ impl Render for Preferences { const AVATAR: &str = "Hide all avatar pictures to improve performance."; const APPEARANCE: &str = "Choose whether the app follows the system theme or uses a light/dark theme."; - const FONT_SIZE: &str = "The base font size for the interface."; - const RADIUS: &str = "Corner radius for UI elements. Empty follows the OS theme."; - const RADIUS_LG: &str = - "Corner radius for large elements like dialogs. Empty follows the OS theme."; - const FOCUS_RING: &str = "Draw a ring around focused controls."; const NIP4E: &str = "Use a dedicated key to encrypt and decrypt messages."; const NIP4E_ROTATION: &str = "Automatically rotate the encryption key every 30 days, keeping the previous key for messages still sealed to it."; const BACKGROUND: &str = "Show an image behind chat and group panels."; @@ -404,7 +309,6 @@ impl Render for Preferences { let nip4e = AppSettings::get_nip4e(cx); let nip4e_rotation_days = AppSettings::get_nip4e_rotation_days(cx); let appearance_mode = AppSettings::get_appearance_mode(cx); - let theme = AppSettings::get_theme(cx); let panel_background = AppSettings::get_panel_background(cx); let panel_background_path = AppSettings::get_panel_background_path(cx); @@ -472,44 +376,6 @@ impl Render for Preferences { cx, )) .child(Separator::horizontal()) - .child( - v_flex() - .w_full() - .gap_3() - .child(settings_row( - "Font Size", - FONT_SIZE, - NumberInput::new(&self.font_size_input).w_24(), - cx, - )) - .child(settings_row( - "Corner Radius", - RADIUS, - NumberInput::new(&self.radius_input).w_24(), - cx, - )) - .child(settings_row( - "Large Corner Radius", - RADIUS_LG, - NumberInput::new(&self.radius_lg_input).w_24(), - cx, - )) - .child(settings_row( - "Focus Ring", - FOCUS_RING, - Switch::new("focus-ring") - .checked(theme.focus_ring) - .on_click(move |_, _window, cx| { - AppSettings::update_theme( - |theme| theme.focus_ring = !theme.focus_ring, - cx, - ); - AppSettings::apply_theme_style(cx); - }), - cx, - )), - ) - .child(Separator::horizontal()) .child( v_flex() .w_full() diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index cc9a72b1..215c03bb 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use ::settings::{AppSettings, AppearanceMode}; +use ::settings::AppearanceMode; use anyhow::Error; #[cfg(not(target_arch = "wasm32"))] use auto_update::AutoUpdater; @@ -14,7 +14,7 @@ use gpui::{ use gpui_kit::component::button::Button; use gpui_kit::component::dock::{DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; use gpui_kit::component::notification::{Notification, NotificationType}; -use gpui_kit::component::{ActiveTheme, Theme, WindowExt, v_flex}; +use gpui_kit::component::{ActiveTheme, WindowExt, v_flex}; use nip4e::{DeviceEvent, DeviceRegistry}; use nip29::{GroupId, GroupKey}; use nostr_sdk::prelude::*; @@ -42,6 +42,31 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Workspace::new(window, cx)) } +/// Apply the appearance mode to the kit and mirror it into the old theme +/// that the speech components still use. +pub fn apply_appearance_mode(mode: AppearanceMode, cx: &mut App) { + match mode { + AppearanceMode::System => { + gpui_native_kit::theme::follow_system(cx); + gpui_kit::component::Theme::sync_system_appearance(None, cx); + } + AppearanceMode::Light => { + gpui_native_kit::theme::set_mode_override( + Some(gpui_native_kit::theme::ThemeVariant::Light), + cx, + ); + gpui_kit::component::Theme::change(gpui_kit::component::ThemeMode::Light, None, cx); + } + AppearanceMode::Dark => { + gpui_native_kit::theme::set_mode_override( + Some(gpui_native_kit::theme::ThemeVariant::Dark), + cx, + ); + gpui_kit::component::Theme::change(gpui_kit::component::ThemeMode::Dark, None, cx); + } + } +} + struct DeviceNotifcation; struct MsgRelayNotification; @@ -93,10 +118,7 @@ impl Workspace { let mut subscriptions = smallvec![]; subscriptions.push(cx.observe_window_appearance(window, |_this, window, cx| { - if AppSettings::get_appearance_mode(cx) == AppearanceMode::System { - Theme::sync_system_appearance(Some(window), cx); - AppSettings::apply_theme_style(cx); - } + gpui_native_kit::theme::Theme::sync_from_window(window, cx); })); subscriptions.push( @@ -536,8 +558,8 @@ impl Workspace { impl Render for Workspace { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - // Dialog, notification and tooltip layers are owned by the framework - // Root that gpui_kit::open_window installed for this window. + // Dialog, notification and tooltip layers previously came from the + // old kit's window root; they move to common dialogs and toasts. div() .id("workspace") .on_action(cx.listener(Self::on_command)) diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 6c710c20..2aed3d25 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -47,13 +47,11 @@ person = { path = "../crates/person" } gpui.workspace = true gpui_platform.workspace = true -gpui-kit.workspace = true gpui_linux.workspace = true gpui_windows.workspace = true gpui_macos.workspace = true -native-theme.workspace = true -native-theme-gpui.workspace = true -flume.workspace = true +gpui-native-kit.workspace = true +speech = { path = "../crates/speech" } reqwest_client.workspace = true log.workspace = true diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 5a2e3665..22f94da7 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -2,18 +2,14 @@ use std::sync::{Arc, Mutex}; use chat_ui::ToggleDictation; use gpui::{ - App, Bounds, Global, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, - WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, - actions, point, px, size, + App, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, WindowBounds, + WindowDecorations, WindowKind, WindowOptions, actions, point, px, size, }; -use gpui_kit::component::{Theme, ThemeMode}; +use gpui_native_kit::theme::ActiveTheme; use gpui_platform::application; -use native_theme::watch::ThemeSubscription; -use native_theme_gpui::apply_system_theme; use nostr_sdk::prelude::SecretKey; -use settings::{AppSettings, AppearanceMode}; use state::{APP_ID, CLIENT_NAME}; -use workspace::TAB_BAR_HEIGHT; +use workspace::{TAB_BAR_HEIGHT, apply_appearance_mode}; actions!(coop, [Quit]); @@ -34,8 +30,9 @@ fn main() { .with_assets(assets::CombinedAssets) .with_http_client(Arc::new(reqwest_client::ReqwestClient::new())) .run(move |cx| { - // Initialize the framework before anything renders - gpui_kit::init(cx); + // Initialize the frameworks before anything renders + gpui_native_kit::init(cx); + speech::init_compat(cx); // Load embedded fonts in assets/fonts load_embedded_fonts(cx); @@ -71,14 +68,7 @@ fn main() { settings::init(cx); let appearance = settings::persisted_appearance_mode(); - let font_size = settings::persisted_theme().font_size; - - if let Err(error) = apply_native_theme(appearance, font_size, cx) { - log::warn!("native theme unavailable ({error})"); - apply_appearance(appearance, cx); - AppSettings::apply_theme_style(cx); - } - start_theme_watcher(cx); + apply_appearance_mode(appearance, cx); // Initialize the nostr client state::init(cx, cli_key); @@ -110,7 +100,7 @@ fn main() { // Set up the window options let opts = WindowOptions { - window_background: WindowBackgroundAppearance::Opaque, + window_background: cx.theme().window_background, window_decorations: Some(WindowDecorations::Client), window_bounds: Some(WindowBounds::Windowed(bounds)), kind: WindowKind::Normal, @@ -127,8 +117,18 @@ fn main() { ..Default::default() }; - gpui_kit::open_window(opts, cx, workspace::init) - .expect("Failed to open window. Please restart the application."); + cx.open_window(opts, |window, cx| { + gpui_native_kit::theme::Theme::sync_from_window(window, cx); + gpui_native_kit::theme::Theme::sync_window_rem_size(window, cx); + window + .observe_window_appearance(|window, cx| { + gpui_native_kit::theme::Theme::sync_from_window(window, cx); + cx.refresh_windows(); + }) + .detach(); + workspace::init(window, cx) + }) + .expect("Failed to open window. Please restart the application."); cx.activate(true); }); @@ -181,72 +181,3 @@ fn quit(_ev: &Quit, cx: &mut App) { log::info!("Gracefully quitting the application . . ."); cx.quit(); } - -/// Apply the native theme to the app, scaled to the user's font size, -/// then layer the persisted theme tuning on top -fn apply_native_theme( - appearance: AppearanceMode, - font_size: f32, - cx: &mut App, -) -> native_theme::Result<()> { - let mut system = native_theme_gpui::SystemTheme::from_system()?; - let native = system.light.defaults.font.size; - - if native > 0.0 { - system.accessibility.text_scaling_factor *= font_size / native; - } - - apply_system_theme(&system, cx); - apply_appearance(appearance, cx); - // Applying the native theme replaces the whole theme, so the user's - // tuning must be layered on afterwards. - AppSettings::apply_theme_style(cx); - - Ok(()) -} - -/// Honor the appearance mode against the installed (native) palettes -fn apply_appearance(appearance: AppearanceMode, cx: &mut App) { - match appearance { - AppearanceMode::System => Theme::sync_system_appearance(None, cx), - AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx), - AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx), - } -} - -/// Holds the watcher subscription so it lives as long as the app. -struct ThemeWatcher { - _subscription: ThemeSubscription, -} - -impl Global for ThemeWatcher {} - -/// Start a background task to watch for OS theme changes and re-apply the native theme. -fn start_theme_watcher(cx: &mut App) { - let (sender, receiver) = flume::unbounded(); - match native_theme::watch::on_theme_change(move |event| { - if let Err(error) = sender.send(event) { - log::warn!("theme change dropped ({error})"); - } - }) { - Ok(subscription) => { - cx.set_global(ThemeWatcher { - _subscription: subscription, - }); - cx.spawn(async move |cx| { - while receiver.recv_async().await.is_ok() { - log::info!("OS theme changed; re-applying"); - cx.update(|cx| { - let appearance = AppSettings::get_appearance_mode(cx); - let font_size = AppSettings::get_theme(cx).font_size; - if let Err(error) = apply_native_theme(appearance, font_size, cx) { - log::warn!("failed to apply updated OS theme ({error})"); - } - }); - } - }) - .detach(); - } - Err(error) => log::warn!("OS theme watching unavailable ({error})"), - } -} diff --git a/web/Cargo.toml b/web/Cargo.toml index 6251560b..be45a59f 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -22,7 +22,8 @@ person = { path = "../crates/person" } gpui.workspace = true gpui_platform.workspace = true gpui_web.workspace = true -gpui-kit.workspace = true +gpui-native-kit.workspace = true +speech = { path = "../crates/speech" } log.workspace = true instant = { workspace = true, features = ["wasm-bindgen"] } diff --git a/web/src/lib.rs b/web/src/lib.rs index abc91bac..45142745 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1,10 +1,10 @@ #![cfg(target_arch = "wasm32")] use std::cell::RefCell; -use std::rc::Rc; use std::sync::Arc; use gpui::*; +use gpui_native_kit::theme::ActiveTheme; use universal_time::{Instant, MonotonicClock, SystemTime, WallClock, define_time_provider}; use wasm_bindgen::prelude::*; @@ -83,16 +83,15 @@ pub fn run() -> Result<(), JsValue> { KeyBinding::new("ctrl-d", chat_ui::ToggleDictation, None), ]); - gpui_kit::init(cx); + gpui_native_kit::init(cx); + speech::init_compat(cx); load_embedded_fonts(cx); - apply_web_fonts(cx); // Initialize settings settings::init(cx); - apply_appearance(settings::persisted_appearance_mode(), cx); - settings::AppSettings::apply_theme_style(cx); + workspace::apply_appearance_mode(settings::persisted_appearance_mode(), cx); // Initialize the nostr client state::init(cx, None); @@ -111,7 +110,20 @@ pub fn run() -> Result<(), JsValue> { // Initialize groups registry nip29::init(cx); - gpui_kit::open_window(WindowOptions::default(), cx, |window, cx| { + let opts = WindowOptions { + window_background: cx.theme().window_background, + ..Default::default() + }; + + cx.open_window(opts, |window, cx| { + gpui_native_kit::theme::Theme::sync_from_window(window, cx); + gpui_native_kit::theme::Theme::sync_window_rem_size(window, cx); + window + .observe_window_appearance(|window, cx| { + gpui_native_kit::theme::Theme::sync_from_window(window, cx); + cx.refresh_windows(); + }) + .detach(); workspace::init(window, cx) }) .expect("Failed to open window. Please restart the application."); @@ -139,31 +151,3 @@ fn load_embedded_fonts(cx: &mut App) { .add_fonts(fonts) .expect("Failed to load bundled fonts"); } - -fn apply_web_fonts(cx: &mut App) { - use gpui_kit::component::Theme; - - const UI_FONT: &str = "Inter"; - - Theme::update(cx, |theme| { - theme.font_family = UI_FONT.into(); - theme.mono_font_family = UI_FONT.into(); - - for config in [&mut theme.light_theme, &mut theme.dark_theme] { - let config = Rc::make_mut(config); - config.font_family = Some(UI_FONT.into()); - config.mono_font_family = Some(UI_FONT.into()); - } - }); -} - -/// Honor the appearance mode against the default palettes -fn apply_appearance(appearance: settings::AppearanceMode, cx: &mut App) { - use gpui_kit::component::{Theme, ThemeMode}; - - match appearance { - settings::AppearanceMode::System => Theme::sync_system_appearance(None, cx), - settings::AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx), - settings::AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx), - } -}