Skip to content

Commit

Permalink
feat(window-state): add js api, closes #254 (#309)
Browse files Browse the repository at this point in the history
* feat(window-state): add js api, closes #254

* symlink tsconfig.json

* update symlink

* Update plugins/window-state/package.json

Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>

* Update Cargo.toml

* move to cmd.rs

* Update plugins/window-state/guest-js/index.ts

---------

Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>
  • Loading branch information
amrbashir and FabianLars authored Apr 17, 2023
1 parent 6f39921 commit e5b07f4
Show file tree
Hide file tree
Showing 10 changed files with 1,083 additions and 779 deletions.
1 change: 1 addition & 0 deletions plugins/window-state/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
4 changes: 2 additions & 2 deletions plugins/window-state/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "tauri-plugin-window-state"
version = "0.1.0"
description = "Save window positions and sizse and restore them when the app is reopened."
description = "Save window positions and sizes and restore them when the app is reopened."
authors.workspace = true
license.workspace = true
edition.workspace = true
Expand All @@ -16,4 +16,4 @@ tauri.workspace = true
log.workspace = true
thiserror.workspace = true
bincode = "1.3"
bitflags = "2"
bitflags = "2"
28 changes: 28 additions & 0 deletions plugins/window-state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
```

You can install the JavaScript Guest bindings using your preferred JavaScript package manager:

> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
```sh
pnpm add https://github.com/tauri-apps/tauri-plugin-window-state
# or
npm add https://github.com/tauri-apps/tauri-plugin-window-state
# or
yarn add https://github.com/tauri-apps/tauri-plugin-window-state
```

## Usage

First you need to register the core plugin with Tauri:
Expand All @@ -47,6 +59,14 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
app.save_window_state(StateFlags::all()); // will save the state of all open windows to disk
```

or through Javascript

```javascript
import { saveWindowState, StateFlags } from "tauri-plugin-window-state-api";

saveWindowState(StateFlags.ALL);
```

To manually restore a windows state from disk you can call the `restore_state()` method exposed by the `WindowExt` trait:

```rust
Expand All @@ -56,6 +76,14 @@ use tauri_plugin_window_state::{WindowExt, StateFlags};
window.restore_state(StateFlags::all()); // will restore the windows state from disk
```

or through Javascript

```javascript
import { restoreStateCurrent, StateFlags } from "tauri-plugin-window-state-api";

restoreStateCurrent(StateFlags.ALL);
```

## Contributing

PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
Expand Down
35 changes: 35 additions & 0 deletions plugins/window-state/guest-js/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { invoke } from "@tauri-apps/api/tauri";
import { WindowLabel, getCurrent } from "@tauri-apps/api/window";

export enum StateFlags {
SIZE = 1 << 0,
POSITION = 1 << 1,
MAXIMIZED = 1 << 2,
VISIBLE = 1 << 3,
DECORATIONS = 1 << 4,
FULLSCREEN = 1 << 5,
ALL = SIZE | POSITION | MAXIMIZED | VISIBLE | DECORATIONS | FULLSCREEN,
}

/**
* Save the state of all open windows to disk.
*/
async function saveWindowState(flags: StateFlags) {
invoke("plugin:window-state|js_save_window_state", { flags });
}

/**
* Restore the state for the specified window from disk.
*/
async function restoreState(label: WindowLabel, flags: StateFlags) {
invoke("plugin:window-state|js_restore_state", { label, flags });
}

/**
* Restore the state for the current window from disk.
*/
async function restoreStateCurrent(flags: StateFlags) {
restoreState(getCurrent().label, flags);
}

export { restoreState, restoreStateCurrent, saveWindowState };
33 changes: 33 additions & 0 deletions plugins/window-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "tauri-plugin-window-state-api",
"version": "0.1.0",
"description": "Save window positions and sizes and restore them when the app is reopened.",
"license": "MIT or APACHE-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
],
"type": "module",
"browser": "dist-js/index.min.js",
"module": "dist-js/index.mjs",
"types": "dist-js/index.d.ts",
"exports": {
"import": "./dist-js/index.mjs",
"types": "./dist-js/index.d.ts",
"browser": "./dist-js/index.min.js"
},
"scripts": {
"build": "rollup -c"
},
"files": [
"dist-js",
"!dist-js/**/*.map",
"README.md",
"LICENSE"
],
"devDependencies": {
"tslib": "^2.4.1"
},
"dependencies": {
"@tauri-apps/api": "^1.2.0"
}
}
11 changes: 11 additions & 0 deletions plugins/window-state/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { readFileSync } from "fs";

import { createConfig } from "../../shared/rollup.config.mjs";

export default createConfig({
input: "guest-js/index.ts",
pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8")
),
external: [/^@tauri-apps\/api/],
});
28 changes: 28 additions & 0 deletions plugins/window-state/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::{AppHandleExt, StateFlags, WindowExt};
use tauri::{command, AppHandle, Manager, Runtime};

#[command]
pub async fn save_window_state<R: Runtime>(
app: AppHandle<R>,
flags: u32,
) -> std::result::Result<(), String> {
let flags = StateFlags::from_bits(flags)
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
app.save_window_state(flags).map_err(|e| e.to_string())?;
Ok(())
}

#[command]
pub async fn restore_state<R: Runtime>(
app: AppHandle<R>,
label: String,
flags: u32,
) -> std::result::Result<(), String> {
let flags = StateFlags::from_bits(flags)
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
app.get_window(&label)
.ok_or_else(|| format!("Couldn't find window with label: {}", label))?
.restore_state(flags)
.map_err(|e| e.to_string())?;
Ok(())
}
29 changes: 27 additions & 2 deletions plugins/window-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use std::{
sync::{Arc, Mutex},
};

mod cmd;

pub const STATE_FILENAME: &str = ".window-state";

#[derive(Debug, thiserror::Error)]
Expand All @@ -34,7 +36,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>;

bitflags! {
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct StateFlags: u32 {
const SIZE = 1 << 0;
const POSITION = 1 << 1;
Expand All @@ -51,7 +53,7 @@ impl Default for StateFlags {
}
}

#[derive(Debug, Default, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
struct WindowState {
width: f64,
height: f64,
Expand All @@ -65,6 +67,7 @@ struct WindowState {

struct WindowStateCache(Arc<Mutex<HashMap<String, WindowState>>>);
pub trait AppHandleExt {
/// Saves all open windows state to disk
fn save_window_state(&self, flags: StateFlags) -> Result<()>;
}

Expand Down Expand Up @@ -94,16 +97,23 @@ impl<R: Runtime> AppHandleExt for tauri::AppHandle<R> {
}

pub trait WindowExt {
/// Restores this window state from disk
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>;
}

impl<R: Runtime> WindowExt for Window<R> {
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()> {
let cache = self.state::<WindowStateCache>();
let mut c = cache.0.lock().unwrap();

let mut should_show = true;

if let Some(state) = c.get(self.label()) {
// avoid restoring the default zeroed state
if *state == WindowState::default() {
return Ok(());
}

if flags.contains(StateFlags::DECORATIONS) {
self.set_decorations(state.decorated)?;
}
Expand Down Expand Up @@ -270,6 +280,10 @@ impl Builder {
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
let flags = self.state_flags;
PluginBuilder::new("window-state")
.invoke_handler(tauri::generate_handler![
cmd::save_window_state,
cmd::restore_state
])
.setup(|app| {
let cache: Arc<Mutex<HashMap<String, WindowState>>> = if let Some(app_dir) =
app.path_resolver().app_config_dir()
Expand Down Expand Up @@ -305,6 +319,17 @@ impl Builder {
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
{
cache
.lock()
.unwrap()
.entry(label.clone())
.or_insert_with(WindowState::default);
}

window.on_window_event(move |e| {
if let WindowEvent::CloseRequested { .. } = e {
let mut c = cache.lock().unwrap();
Expand Down
1 change: 1 addition & 0 deletions plugins/window-state/tsconfig.json
Loading

0 comments on commit e5b07f4

Please sign in to comment.