Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add show manager shortcut plus refactoring #419

Merged
merged 3 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion crates/deskulpt-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
26 changes: 3 additions & 23 deletions crates/deskulpt-core/src/commands/update_shortcut.rs
Original file line number Diff line number Diff line change
@@ -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
///
Expand All @@ -31,12 +17,6 @@ pub async fn update_shortcut<R: Runtime>(
old_shortcut: Option<String>,
new_shortcut: Option<String>,
) -> 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(())
}
21 changes: 18 additions & 3 deletions crates/deskulpt-core/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -28,16 +28,22 @@ enum Theme {
#[serde(rename_all = "camelCase")]
pub struct Shortcuts {
/// For toggling canvas click-through.
#[serde(default)]
pub toggle_canvas: Option<String>,
/// For opening the manager window.
#[serde(default)]
pub open_manager: Option<String>,
}

/// Application-wide settings.
#[derive(Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct AppSettings {
/// The application theme.
#[serde(default)]
theme: Theme,
/// The keyboard shortcuts.
#[serde(default)]
shortcuts: Shortcuts,
}

Expand All @@ -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<String, WidgetSettings>,
}

Expand All @@ -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)
}

Expand All @@ -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(())
}

Expand Down
142 changes: 89 additions & 53 deletions crates/deskulpt-core/src/shortcuts.rs
Original file line number Diff line number Diff line change
@@ -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<R: Runtime>: Manager<R> + GlobalShortcutExt<R> {
/// 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<R: Runtime>: Manager<R> + GlobalShortcutExt<R> {
/// 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<R: Runtime> ShortcutsExt<R> for App<R> {}
Expand Down
4 changes: 2 additions & 2 deletions crates/deskulpt-core/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ fn on_menu_event<R: Runtime>(app_handle: &AppHandle<R>, 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" => {
Expand Down
4 changes: 2 additions & 2 deletions crates/deskulpt-core/src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ pub trait WindowExt<R: Runtime>: Manager<R> {
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"))?;
Expand Down
Loading