From 72409160da388a913b3be963bfe980f25b0db5c4 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Thu, 10 Aug 2023 16:46:01 +0300 Subject: [PATCH] feat(window-state): add option to specify filename, closes #516 --- plugins/window-state/guest-js/index.ts | 16 ++- plugins/window-state/src/cmd.rs | 11 ++ plugins/window-state/src/lib.rs | 162 ++++++++++++++++++------- 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/plugins/window-state/guest-js/index.ts b/plugins/window-state/guest-js/index.ts index 680b5f35..189afe25 100644 --- a/plugins/window-state/guest-js/index.ts +++ b/plugins/window-state/guest-js/index.ts @@ -18,12 +18,19 @@ async function saveWindowState(flags: StateFlags): Promise { return invoke("plugin:window-state|save_window_state", { flags }); } +/** + * Restore the state of all open windows from disk. + */ +async function restoreWindowState(flags: StateFlags): Promise { + return invoke("plugin:window-state|restore_window_state", { flags }); +} + /** * Restore the state for the specified window from disk. */ async function restoreState( label: WindowLabel, - flags: StateFlags, + flags: StateFlags ): Promise { return invoke("plugin:window-state|restore_state", { label, flags }); } @@ -35,4 +42,9 @@ async function restoreStateCurrent(flags: StateFlags): Promise { return restoreState(getCurrent().label, flags); } -export { restoreState, restoreStateCurrent, saveWindowState }; +export { + restoreState, + restoreWindowState, + restoreStateCurrent, + saveWindowState, +}; diff --git a/plugins/window-state/src/cmd.rs b/plugins/window-state/src/cmd.rs index 65690db4..35bbbe3b 100644 --- a/plugins/window-state/src/cmd.rs +++ b/plugins/window-state/src/cmd.rs @@ -12,6 +12,17 @@ pub async fn save_window_state( Ok(()) } +#[command] +pub async fn restore_window_state( + app: AppHandle, + flags: u32, +) -> std::result::Result<(), String> { + let flags = StateFlags::from_bits(flags) + .ok_or_else(|| format!("Invalid state flags bits: {}", flags))?; + app.restore_window_state(flags).map_err(|e| e.to_string())?; + Ok(()) +} + #[command] pub async fn restore_state( app: AppHandle, diff --git a/plugins/window-state/src/lib.rs b/plugins/window-state/src/lib.rs index f17201ca..d27d2253 100644 --- a/plugins/window-state/src/lib.rs +++ b/plugins/window-state/src/lib.rs @@ -19,23 +19,47 @@ use std::{ mod cmd; +/// The default filename used by the plugin. pub const STATE_FILENAME: &str = ".window-state"; +/// window-state plugin errors. #[derive(Debug, thiserror::Error)] pub enum Error { + /// I/O errors. #[error(transparent)] Io(#[from] std::io::Error), + /// tauri crate errors. #[error(transparent)] Tauri(#[from] tauri::Error), + /// tauri crate [api](tauri::api) errors. #[error(transparent)] TauriApi(#[from] tauri::api::Error), + /// bincode crate errors. #[error(transparent)] Bincode(#[from] Box), } +/// window-state result type. pub type Result = std::result::Result; +trait MonitorExt { + fn contains(&self, position: PhysicalPosition) -> bool; +} + +impl MonitorExt for Monitor { + fn contains(&self, position: PhysicalPosition) -> bool { + let PhysicalPosition { x, y } = *self.position(); + let PhysicalSize { width, height } = *self.size(); + + x < position.x as _ + && position.x < (x + width as i32) + && y < position.y as _ + && position.y < (y + height as i32) + } +} + bitflags! { + /// The window states to be saved and restored. #[derive(Clone, Copy, Debug)] pub struct StateFlags: u32 { const SIZE = 1 << 0; @@ -81,15 +105,38 @@ impl Default for WindowState { } struct WindowStateCache(Arc>>); -pub trait AppHandleExt { - /// Saves all open windows state to disk - fn save_window_state(&self, flags: StateFlags) -> Result<()>; + +trait AppHandleExtInternal { + fn setup_window_state>(&self, filename: S) -> Result<()>; + fn save_window_state>(&self, flags: StateFlags, filename: S) -> Result<()>; } -impl AppHandleExt for tauri::AppHandle { - fn save_window_state(&self, flags: StateFlags) -> Result<()> { +impl AppHandleExtInternal for tauri::AppHandle { + fn setup_window_state>(&self, filename: S) -> Result<()> { + let filename = filename.as_ref(); + let cache: Arc>> = + if let Some(app_dir) = self.path_resolver().app_config_dir() { + let state_path = app_dir.join(filename); + if state_path.exists() { + Arc::new(Mutex::new( + tauri::api::file::read_binary(state_path) + .map_err(Error::TauriApi) + .and_then(|state| bincode::deserialize(&state).map_err(Into::into)) + .unwrap_or_default(), + )) + } else { + Default::default() + } + } else { + Default::default() + }; + self.manage(WindowStateCache(cache)); + Ok(()) + } + + fn save_window_state>(&self, flags: StateFlags, filename: S) -> Result<()> { if let Some(app_dir) = self.path_resolver().app_config_dir() { - let state_path = app_dir.join(STATE_FILENAME); + let state_path = app_dir.join(filename.as_ref()); let cache = self.state::(); let mut state = cache.0.lock().unwrap(); for (label, s) in state.iter_mut() { @@ -111,6 +158,45 @@ impl AppHandleExt for tauri::AppHandle { } } +/// A helper trait that provides convenience methods to +/// call the plugin APIs. +pub trait AppHandleExt { + /// Saves all open windows state to disk using the [default filename](crate::STATE_FILENAME). + fn save_window_state(&self, flags: StateFlags) -> Result<()>; + /// Saves all open windows state to disk using the specified filename. + fn save_window_state_with_filename>( + &self, + flags: StateFlags, + filename: S, + ) -> Result<()>; + /// Restores all open windows state from disk. + fn restore_window_state(&self, flags: StateFlags) -> Result<()>; +} + +impl AppHandleExt for tauri::AppHandle { + fn save_window_state(&self, flags: StateFlags) -> Result<()> { + AppHandleExtInternal::save_window_state(self, flags, STATE_FILENAME) + } + + fn save_window_state_with_filename>( + &self, + flags: StateFlags, + filename: S, + ) -> Result<()> { + AppHandleExtInternal::save_window_state(self, flags, filename) + } + + fn restore_window_state(&self, flags: StateFlags) -> Result<()> { + for window in self.windows().values() { + window.restore_state(flags)?; + } + + Ok(()) + } +} + +/// A helper trait that provides convenience methods to +/// call the plugin APIs for a specific window. pub trait WindowExt { /// Restores this window state from disk fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>; @@ -265,14 +351,27 @@ impl WindowExtInternal for Window { } } +/// The window-state plugin builder #[derive(Default)] pub struct Builder { denylist: HashSet, skip_initial_state: HashSet, state_flags: StateFlags, + filename: Option, } impl Builder { + /// Create a new window-state plugin builder. + pub fn new() -> Self { + Self::default() + } + + /// Sets the state flags to control what state gets restored and saved. + pub fn with_filename>(mut self, filename: S) -> Self { + self.filename.replace(filename.as_ref().to_string()); + self + } + /// Sets the state flags to control what state gets restored and saved. pub fn with_state_flags(mut self, flags: StateFlags) -> Self { self.state_flags = flags; @@ -293,33 +392,15 @@ impl Builder { } pub fn build(self) -> TauriPlugin { - let flags = self.state_flags; + let filename = self.filename.unwrap_or_else(|| STATE_FILENAME.to_string()); + let filename_c = filename.clone(); PluginBuilder::new("window-state") .invoke_handler(tauri::generate_handler![ cmd::save_window_state, + cmd::restore_window_state, cmd::restore_state ]) - .setup(|app| { - let cache: Arc>> = if let Some(app_dir) = - app.path_resolver().app_config_dir() - { - let state_path = app_dir.join(STATE_FILENAME); - if state_path.exists() { - Arc::new(Mutex::new( - tauri::api::file::read_binary(state_path) - .map_err(Error::TauriApi) - .and_then(|state| bincode::deserialize(&state).map_err(Into::into)) - .unwrap_or_default(), - )) - } else { - Default::default() - } - } else { - Default::default() - }; - app.manage(WindowStateCache(cache)); - Ok(()) - }) + .setup(|app| app.setup_window_state(filename).map_err(Into::into)) .on_webview_ready(move |window| { if self.denylist.contains(window.label()) { return; @@ -333,7 +414,6 @@ impl Builder { let cache = cache.0.clone(); let label = window.label().to_string(); let window_clone = window.clone(); - let flags = self.state_flags; // insert a default state if this window should be tracked and // the disk cache doesn't have a state for it @@ -349,32 +429,20 @@ impl Builder { if let WindowEvent::CloseRequested { .. } = e { let mut c = cache.lock().unwrap(); if let Some(state) = c.get_mut(&label) { - let _ = window_clone.update_state(state, flags); + let _ = window_clone.update_state(state, self.state_flags); } } }); }) .on_event(move |app, event| { if let RunEvent::Exit = event { - let _ = app.save_window_state(flags); + let _ = AppHandleExtInternal::save_window_state( + app, + self.state_flags, + filename_c.clone(), + ); } }) .build() } } - -trait MonitorExt { - fn contains(&self, position: PhysicalPosition) -> bool; -} - -impl MonitorExt for Monitor { - fn contains(&self, position: PhysicalPosition) -> bool { - let PhysicalPosition { x, y } = *self.position(); - let PhysicalSize { width, height } = *self.size(); - - x < position.x as _ - && position.x < (x + width as i32) - && y < position.y as _ - && position.y < (y + height as i32) - } -}