migrate theming to gpui-native-kit
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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<Preferences> {
|
||||
cx.new(|cx| Preferences::new(window, cx))
|
||||
}
|
||||
|
||||
pub struct Preferences {
|
||||
file_input: Entity<InputState>,
|
||||
font_size_input: Entity<InputState>,
|
||||
radius_input: Entity<InputState>,
|
||||
radius_lg_input: Entity<InputState>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mint_input: Entity<InputState>,
|
||||
#[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<InputState>,
|
||||
range: (f32, f32),
|
||||
cx: &mut Context<Self>,
|
||||
persist: impl Fn(Option<f32>, &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::<f32>()
|
||||
.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<Self>) {
|
||||
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()
|
||||
|
||||
@@ -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<Workspace> {
|
||||
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<Self>) -> 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))
|
||||
|
||||
Reference in New Issue
Block a user