From 53aace8142914f533d37eae793f1ce127d8cfadc Mon Sep 17 00:00:00 2001 From: Yao Xiao <108576690+Charlie-XIAO@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:25:19 -0500 Subject: [PATCH] refactor: add show manager shortcut plus refactoring (#419) * refactor: add show manager shortcut plus refactoring * show-manager -> open-manager * show-manager -> open-manager more --- Cargo.lock | 11 +- Cargo.toml | 2 +- crates/deskulpt-core/Cargo.toml | 2 +- .../src/commands/update_shortcut.rs | 26 +--- crates/deskulpt-core/src/settings.rs | 21 ++- crates/deskulpt-core/src/shortcuts.rs | 142 +++++++++++------- crates/deskulpt-core/src/tray.rs | 4 +- crates/deskulpt-core/src/window/mod.rs | 4 +- src/manager/components/Settings/index.tsx | 14 +- src/types/backend/settings.ts | 1 + 10 files changed, 129 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a26b2d9f..1f0e8926 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,15 +241,6 @@ dependencies = [ "vsimd", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -957,7 +948,6 @@ name = "deskulpt-core" version = "0.0.1" dependencies = [ "anyhow", - "bincode", "deskulpt-plugin", "deskulpt-plugin-fs", "deskulpt-plugin-sys", @@ -966,6 +956,7 @@ dependencies = [ "once_cell", "open", "oxc", + "paste", "path-clean", "rolldown", "rolldown_common", diff --git a/Cargo.toml b/Cargo.toml index bbe7f3e7..5f8afbe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,12 @@ version = "0.0.1" [workspace.dependencies] anyhow = "1.0.87" -bincode = "1.3.3" dunce = "1.0.5" objc2 = "0.5.2" once_cell = "1.20.2" open = "5.3.1" oxc = "0.43.0" +paste = "1.0.15" path-clean = "1.0.1" rolldown = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-beta.1" } rolldown_common = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-beta.1" } diff --git a/crates/deskulpt-core/Cargo.toml b/crates/deskulpt-core/Cargo.toml index 725bc496..aef09c80 100644 --- a/crates/deskulpt-core/Cargo.toml +++ b/crates/deskulpt-core/Cargo.toml @@ -11,11 +11,11 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } -bincode = { workspace = true } dunce = { workspace = true } once_cell = { workspace = true } open = { workspace = true, features = ["shellexecute-on-windows"] } oxc = { workspace = true } +paste = { workspace = true } path-clean = { workspace = true } rolldown = { workspace = true } rolldown_common = { workspace = true } diff --git a/crates/deskulpt-core/src/commands/update_shortcut.rs b/crates/deskulpt-core/src/commands/update_shortcut.rs index d65c13c8..71666bd2 100644 --- a/crates/deskulpt-core/src/commands/update_shortcut.rs +++ b/crates/deskulpt-core/src/commands/update_shortcut.rs @@ -1,23 +1,9 @@ -use serde::Deserialize; use tauri::{command, AppHandle, Runtime}; use super::error::CmdResult; -use crate::shortcuts::ShortcutsExt; +use crate::shortcuts::{ShortcutKey, ShortcutsExt}; -/// The key of the shortcut to update. -/// -/// This corresponds to `keyof Shortcuts` in TypeScript. -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum ShortcutKey { - ToggleCanvas, -} - -/// Update a shortcut registered in the application. -/// -/// This command will compare the old and new shortcuts and perform an update -/// only if it has changed. In that case, the old shortcut (if exists) will be -/// unregistered and the new shortcut (if exists) will be registered. +/// Wrapper of [`update_shortcut`](ShortcutsExt::update_shortcut). /// /// ### Errors /// @@ -31,12 +17,6 @@ pub async fn update_shortcut( old_shortcut: Option, new_shortcut: Option, ) -> CmdResult<()> { - match key { - ShortcutKey::ToggleCanvas => { - app_handle - .update_toggle_canvas_shortcut(old_shortcut.as_deref(), new_shortcut.as_deref())?; - }, - } - + app_handle.update_shortcut(key, old_shortcut.as_deref(), new_shortcut.as_deref())?; Ok(()) } diff --git a/crates/deskulpt-core/src/settings.rs b/crates/deskulpt-core/src/settings.rs index 9870fc94..7dc01a7a 100644 --- a/crates/deskulpt-core/src/settings.rs +++ b/crates/deskulpt-core/src/settings.rs @@ -9,7 +9,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; /// The settings file name in the persistence directory. -static SETTINGS_FILE: &str = "settings.bin"; +static SETTINGS_FILE: &str = "settings.json"; /// Light/dark theme of the application. #[derive(Default, Deserialize, Serialize)] @@ -28,7 +28,11 @@ enum Theme { #[serde(rename_all = "camelCase")] pub struct Shortcuts { /// For toggling canvas click-through. + #[serde(default)] pub toggle_canvas: Option, + /// For opening the manager window. + #[serde(default)] + pub open_manager: Option, } /// Application-wide settings. @@ -36,8 +40,10 @@ pub struct Shortcuts { #[serde(rename_all = "camelCase")] struct AppSettings { /// The application theme. + #[serde(default)] theme: Theme, /// The keyboard shortcuts. + #[serde(default)] shortcuts: Shortcuts, } @@ -49,20 +55,29 @@ struct AppSettings { #[serde(rename_all = "camelCase")] struct WidgetSettings { /// The leftmost x-coordinate in pixels. + #[serde(default)] x: i32, /// The topmost y-coordinate in pixels. + #[serde(default)] y: i32, /// The opacity in percentage. + #[serde(default = "default_opacity")] opacity: i32, } +fn default_opacity() -> i32 { + 100 +} + /// Full settings of the application. #[derive(Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Settings { /// Application-wide settings. + #[serde(default)] app: AppSettings, /// The mapping from widget IDs to their respective settings. + #[serde(default)] widgets: HashMap, } @@ -77,7 +92,7 @@ impl Settings { } let file = File::open(settings_path)?; let reader = BufReader::new(file); - let settings: Settings = bincode::deserialize_from(reader)?; + let settings: Settings = serde_json::from_reader(reader)?; Ok(settings) } @@ -92,7 +107,7 @@ impl Settings { } let file = File::create(persist_dir.join(SETTINGS_FILE))?; let writer = BufWriter::new(file); - bincode::serialize_into(writer, self)?; + serde_json::to_writer(writer, self)?; Ok(()) } diff --git a/crates/deskulpt-core/src/shortcuts.rs b/crates/deskulpt-core/src/shortcuts.rs index 3fc6de4e..e5cc5b75 100644 --- a/crates/deskulpt-core/src/shortcuts.rs +++ b/crates/deskulpt-core/src/shortcuts.rs @@ -1,78 +1,114 @@ -//! Keyboard shortcut registration. +//! Keyboard shortcut management. use anyhow::{bail, Result}; +use paste::paste; +use serde::Deserialize; use tauri::{App, AppHandle, Manager, Runtime}; use tauri_plugin_global_shortcut::{GlobalShortcutExt, ShortcutState}; use crate::settings::Settings; use crate::states::StatesExtCanvasClickThrough; +use crate::WindowExt; -/// Implement a shortcut update function in [`ShortcutsExt`]. +/// Implement [`ShortcutKey`] and [`ShortcutsExt`] for the given shortcuts. /// -/// The first argument is the name of the function. The second argument is the -/// top-level docstring. The third argument is the listener to be registered for -/// the shortcut. -macro_rules! impl_update_shortcut { - ($method: ident, $shortcut: expr, $listener: expr) => { - #[doc = concat!("Update the keyboard shortcut `", $shortcut, "`.")] - /// - /// This will compare the old and new shortcut, and update only when they - /// are different. For each changed shortcut, the old one (if exists) will - /// be unregistered and the new one (if exists) will be registered. - fn $method(&self, old_shortcut: Option<&str>, new_shortcut: Option<&str>) -> Result<()> { - if old_shortcut == new_shortcut { - return Ok(()); +/// This macro takes a list of `key => listener` pairs, where `key` corresponds +/// to the keys of [`Shortcuts`](crate::settings::Shortcuts) and `listener` is +/// the corresponding shortcut handler callback. +macro_rules! impl_shortcuts { + ($($key: ident => $listener: expr),* $(,)?) => { + paste! { + /// Keyboard shortcuts registered in the application. + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum ShortcutKey { + $( + [<$key:upper:camel>], // lower_snake_case => UpperCamelCase + )* } - let manager = self.global_shortcut(); + } - if let Some(shortcut) = old_shortcut { - if !manager.is_registered(shortcut) { - bail!("Failed to unregister '{shortcut}' because it is not registered yet"); + /// Extension trait for keyboard shortcuts. + pub trait ShortcutsExt: Manager + GlobalShortcutExt { + /// Initialize keyboard shortcuts according to the initial settings. + /// + /// If any shortcut fails to be registered, the initial settings will be + /// modified to remove that shortcut. This is to prevent the application + /// from panicking only due to non-critical failures, and also sync this + /// information to the frontend. + fn init_shortcuts(&self, settings: &mut Settings) { + let shortcuts = settings.shortcuts_mut(); + paste! { + $( + if let Err(e) = self.update_shortcut( + ShortcutKey::[<$key:upper:camel>], + None, + shortcuts.$key.as_deref(), + ) { + eprintln!("{}: {}", stringify!($key), e); + shortcuts.$key = None; + } + )* } - manager.unregister(shortcut)?; } - if let Some(shortcut) = new_shortcut { - if manager.is_registered(shortcut) { - bail!("Failed to register '{shortcut}' because it is already registered"); + /// Update a shortcut registered in the application. + /// + /// This function will compare the old and new shortcuts and perform an update + /// only if it has changed. In that case, the old shortcut (if exists) will be + /// unregistered and the new shortcut (if exists) will be registered. + fn update_shortcut( + &self, + key: ShortcutKey, + old_shortcut: Option<&str>, + new_shortcut: Option<&str>, + ) -> Result<()> { + if old_shortcut == new_shortcut { + return Ok(()); } - manager.on_shortcut(shortcut, $listener)?; - } + let manager = self.global_shortcut(); - Ok(()) - } - }; -} + if let Some(shortcut) = old_shortcut { + if !manager.is_registered(shortcut) { + bail!("Cannot unregister '{shortcut}': not registered yet"); + } + manager.unregister(shortcut)?; + } -/// Extension trait for keyboard shortcuts. -pub trait ShortcutsExt: Manager + GlobalShortcutExt { - /// Initialize keyboard shortcuts according to the initial settings. - /// - /// If any shortcut fails to be registered, the initial settings will be - /// modified to remove that shortcut. This is to prevent the application - /// from panicking only due to non-critical failures, and also sync this - /// information to the frontend. - fn init_shortcuts(&self, settings: &mut Settings) { - let shortcuts = settings.shortcuts_mut(); + if let Some(shortcut) = new_shortcut { + if manager.is_registered(shortcut) { + bail!("Cannot register '{shortcut}': already registered"); + } + paste! { + match key { + $( + ShortcutKey::[<$key:upper:camel>] => { + manager.on_shortcut(shortcut, $listener)?; + }, + )* + } + } + } - if let Err(e) = self.update_toggle_canvas_shortcut(None, shortcuts.toggle_canvas.as_deref()) - { - eprintln!("{e}"); - shortcuts.toggle_canvas = None; + Ok(()) + } } - } + }; +} - impl_update_shortcut!( - update_toggle_canvas_shortcut, - "toggle_canvas", - |app_handle, _, event| { - if event.state == ShortcutState::Pressed { - if let Err(e) = app_handle.toggle_canvas_click_through() { - eprintln!("Failed to toggle canvas click-through: {e}"); - } +impl_shortcuts! { + toggle_canvas => |app_handle, _, event| { + if event.state == ShortcutState::Pressed { + if let Err(e) = app_handle.toggle_canvas_click_through() { + eprintln!("Failed to toggle canvas click-through: {e}"); } } - ); + }, + open_manager => |app_handle, _, _| { + if let Err(e) = app_handle.open_manager() { + eprintln!("Failed to open the manager window: {e}"); + } + }, } impl ShortcutsExt for App {} diff --git a/crates/deskulpt-core/src/tray.rs b/crates/deskulpt-core/src/tray.rs index 6f03f1ec..b6ef1938 100644 --- a/crates/deskulpt-core/src/tray.rs +++ b/crates/deskulpt-core/src/tray.rs @@ -63,8 +63,8 @@ fn on_menu_event(app_handle: &AppHandle, event: MenuEvent) { } }, "tray-manage" => { - if let Err(e) = app_handle.show_manager() { - eprintln!("Error showing manager window: {}", e); + if let Err(e) = app_handle.open_manager() { + eprintln!("Error opening manager window: {}", e); } }, "tray-exit" => { diff --git a/crates/deskulpt-core/src/window/mod.rs b/crates/deskulpt-core/src/window/mod.rs index fdef8ba5..e67af54a 100644 --- a/crates/deskulpt-core/src/window/mod.rs +++ b/crates/deskulpt-core/src/window/mod.rs @@ -76,8 +76,8 @@ pub trait WindowExt: Manager { Ok(()) } - /// Show the manager window. - fn show_manager(&self) -> Result<()> { + /// Open the manager window. + fn open_manager(&self) -> Result<()> { let manager = self .get_webview_window("manager") .ok_or(anyhow!("Manager window not found"))?; diff --git a/src/manager/components/Settings/index.tsx b/src/manager/components/Settings/index.tsx index 8e0a9041..ebef03ef 100644 --- a/src/manager/components/Settings/index.tsx +++ b/src/manager/components/Settings/index.tsx @@ -7,21 +7,29 @@ import SectionTable from "./SectionTable"; const Settings = memo(() => { return ( - + Toggle canvas click-through, i.e., sink or float the canvas. If the canvas is sunk (click-through), you can interact with the - desktop but not the widgets. If the canvas is floating, you can - interact with the widgets but not the desktop. + desktop but not the widgets. If the canvas is floating (not + click-through), you can interact with the widgets but not the + desktop. Toggle Canvas + + Open this manager window. + Open Manager + + + + diff --git a/src/types/backend/settings.ts b/src/types/backend/settings.ts index 83976d3a..ff154ebf 100644 --- a/src/types/backend/settings.ts +++ b/src/types/backend/settings.ts @@ -5,6 +5,7 @@ export enum Theme { export interface Shortcuts { toggleCanvas: string | null; + openManager: string | null; } export interface AppSettings {