diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json
index b88a821..7b4fcc2 100644
--- a/permissions/schemas/schema.json
+++ b/permissions/schemas/schema.json
@@ -294,6 +294,34 @@
"PermissionKind": {
"type": "string",
"oneOf": [
+ {
+ "description": "allow-set-window-buttons-inset -> Enables the set-window-buttons-inset command without any pre-configured scope.",
+ "type": "string",
+ "enum": [
+ "allow-set-window-buttons-inset"
+ ]
+ },
+ {
+ "description": "deny-set-window-buttons-inset -> Denies the set-window-buttons-inset command without any pre-configured scope.",
+ "type": "string",
+ "enum": [
+ "deny-set-window-buttons-inset"
+ ]
+ },
+ {
+ "description": "allow-set-window-buttons-inset -> Enables the set_window_buttons_inset command without any pre-configured scope.",
+ "type": "string",
+ "enum": [
+ "allow-set-window-buttons-inset"
+ ]
+ },
+ {
+ "description": "deny-set-window-buttons-inset -> Denies the set_window_buttons_inset command without any pre-configured scope.",
+ "type": "string",
+ "enum": [
+ "deny-set-window-buttons-inset"
+ ]
+ },
{
"description": "allow-show-snap-overlay -> Enables the show_snap_overlay command without any pre-configured scope.",
"type": "string",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..2befe8c
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,307 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@tauri-apps/api':
+ specifier: '>=2.0.0-beta.24'
+ version: 2.0.0-rc.3
+ devDependencies:
+ '@rollup/plugin-typescript':
+ specifier: ^11.1.6
+ version: 11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4)
+ rollup:
+ specifier: ^4.9.6
+ version: 4.21.1
+ tslib:
+ specifier: ^2.6.2
+ version: 2.7.0
+ typescript:
+ specifier: ^5.3.3
+ version: 5.5.4
+
+packages:
+
+ '@rollup/plugin-typescript@11.1.6':
+ resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.14.0||^3.0.0||^4.0.0
+ tslib: '*'
+ typescript: '>=3.7.0'
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ tslib:
+ optional: true
+
+ '@rollup/pluginutils@5.1.0':
+ resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.21.1':
+ resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.21.1':
+ resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.21.1':
+ resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.21.1':
+ resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.21.1':
+ resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.21.1':
+ resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.21.1':
+ resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.21.1':
+ resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.21.1':
+ resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.21.1':
+ resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.21.1':
+ resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.21.1':
+ resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.21.1':
+ resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-win32-arm64-msvc@4.21.1':
+ resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.21.1':
+ resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.21.1':
+ resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@tauri-apps/api@2.0.0-rc.3':
+ resolution: {integrity: sha512-k1erUfnoOFJwL5VNFZz0BQZ2agNstG7CNOjwpdWMl1vOaVuSn4DhJtXB0Deh9lZaaDlfrykKOyZs9c3XXpMi5Q==}
+
+ '@types/estree@1.0.5':
+ resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.15.1:
+ resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+ engines: {node: '>= 0.4'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+
+ rollup@4.21.1:
+ resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tslib@2.7.0:
+ resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+
+ typescript@5.5.4:
+ resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+snapshots:
+
+ '@rollup/plugin-typescript@11.1.6(rollup@4.21.1)(tslib@2.7.0)(typescript@5.5.4)':
+ dependencies:
+ '@rollup/pluginutils': 5.1.0(rollup@4.21.1)
+ resolve: 1.22.8
+ typescript: 5.5.4
+ optionalDependencies:
+ rollup: 4.21.1
+ tslib: 2.7.0
+
+ '@rollup/pluginutils@5.1.0(rollup@4.21.1)':
+ dependencies:
+ '@types/estree': 1.0.5
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ optionalDependencies:
+ rollup: 4.21.1
+
+ '@rollup/rollup-android-arm-eabi@4.21.1':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.21.1':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.21.1':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.21.1':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.21.1':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.21.1':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.21.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.21.1':
+ optional: true
+
+ '@tauri-apps/api@2.0.0-rc.3': {}
+
+ '@types/estree@1.0.5': {}
+
+ estree-walker@2.0.2: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ is-core-module@2.15.1:
+ dependencies:
+ hasown: 2.0.2
+
+ path-parse@1.0.7: {}
+
+ picomatch@2.3.1: {}
+
+ resolve@1.22.8:
+ dependencies:
+ is-core-module: 2.15.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ rollup@4.21.1:
+ dependencies:
+ '@types/estree': 1.0.5
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.21.1
+ '@rollup/rollup-android-arm64': 4.21.1
+ '@rollup/rollup-darwin-arm64': 4.21.1
+ '@rollup/rollup-darwin-x64': 4.21.1
+ '@rollup/rollup-linux-arm-gnueabihf': 4.21.1
+ '@rollup/rollup-linux-arm-musleabihf': 4.21.1
+ '@rollup/rollup-linux-arm64-gnu': 4.21.1
+ '@rollup/rollup-linux-arm64-musl': 4.21.1
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1
+ '@rollup/rollup-linux-riscv64-gnu': 4.21.1
+ '@rollup/rollup-linux-s390x-gnu': 4.21.1
+ '@rollup/rollup-linux-x64-gnu': 4.21.1
+ '@rollup/rollup-linux-x64-musl': 4.21.1
+ '@rollup/rollup-win32-arm64-msvc': 4.21.1
+ '@rollup/rollup-win32-ia32-msvc': 4.21.1
+ '@rollup/rollup-win32-x64-msvc': 4.21.1
+ fsevents: 2.3.3
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tslib@2.7.0: {}
+
+ typescript@5.5.4: {}
diff --git a/src/commands.rs b/src/commands.rs
index 97df025..a1415b2 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -1,4 +1,6 @@
-use tauri;
+use tauri::{LogicalPosition, Manager, Result, Runtime, WebviewWindow};
+
+use crate::WebviewWindowExt;
#[tauri::command]
pub async fn show_snap_overlay() {
@@ -19,3 +21,25 @@ pub async fn show_snap_overlay() {
enigo.key_click(Key::Alt);
}
}
+
+#[tauri::command]
+pub async fn set_window_buttons_inset(
+ window: WebviewWindow,
+ inset: Option>,
+ target_label: Option,
+) -> Result<()> {
+ #[cfg(target_os = "macos")]
+ {
+ let target = match target_label {
+ Some(label) => window
+ .get_webview_window(&label)
+ .ok_or_else(|| tauri::Error::WindowNotFound)?,
+ None => window,
+ };
+
+ target.set_window_buttons_inset(inset)
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ "This command is only supported on macOS."
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0d93c72..2d51ff9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,9 +2,8 @@ use std::collections::HashMap;
use std::sync::Arc;
use macos::nswindow_delegates;
-use parking_lot::RwLock;
use tauri::plugin::{Builder, TauriPlugin};
-use tauri::{Emitter, Error, Listener, LogicalPosition, Manager, Runtime, WebviewWindow};
+use tauri::{Emitter, Listener, LogicalPosition, Manager, Runtime, WebviewWindow};
#[cfg(target_os = "macos")]
#[macro_use]
@@ -15,6 +14,7 @@ mod commands;
#[cfg(target_os = "macos")]
mod macos;
+#[cfg(target_os = "macos")]
#[repr(u32)]
pub enum NSWindowLevel {
NSNormalWindowLevel = 0,
@@ -26,6 +26,7 @@ pub enum NSWindowLevel {
NSScreenSaverWindowLevel = 1000,
}
+#[cfg(target_os = "macos")]
impl From for NSWindowLevel {
fn from(s: String) -> Self {
match s.as_str() {
@@ -47,23 +48,23 @@ impl From for NSWindowLevel {
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the decorum APIs.
pub trait WebviewWindowExt {
- fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error>;
+ fn create_overlay_titlebar(&self) -> tauri::Result<()>;
+
#[cfg(target_os = "macos")]
- fn set_traffic_lights_inset(
- &self,
- inset: Option>,
- ) -> Result<&WebviewWindow, Error>;
+ fn set_window_buttons_inset(&self, options: Option>) -> tauri::Result<()>;
+
#[cfg(target_os = "macos")]
- fn make_transparent(&self) -> Result<&WebviewWindow, Error>;
+ fn make_transparent(&self) -> tauri::Result<()>;
+
#[cfg(target_os = "macos")]
- fn set_window_level(&self, level: NSWindowLevel) -> Result<&WebviewWindow, Error>;
+ fn set_window_level(&self, level: NSWindowLevel) -> tauri::Result<()>;
}
-impl<'a> WebviewWindowExt for WebviewWindow {
+impl WebviewWindowExt for WebviewWindow {
/// Create a custom titlebar overlay.
/// This will remove the default titlebar and create a draggable area for the titlebar.
/// On Windows, it will also create custom window controls.
- fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error> {
+ fn create_overlay_titlebar(&self) -> tauri::Result<()> {
#[cfg(target_os = "windows")]
self.set_decorations(false)?;
@@ -119,93 +120,116 @@ impl<'a> WebviewWindowExt for WebviewWindow {
}
});
- Ok(self)
+ Ok(())
+ }
+
+ /// Set the window background to transparent.
+ /// This helper function is different from Tauri's default
+ /// as it doesn't use the `transparent` flag or macOS Private APIs.
+ #[cfg(target_os = "macos")]
+ fn make_transparent(&self) -> tauri::Result<()> {
+ use cocoa::{
+ base::{id, nil},
+ foundation::NSString,
+ };
+
+ // Make webview background transparent
+ self.with_webview(|webview| unsafe {
+ let id = webview.inner();
+ let no: id = msg_send![class!(NSNumber), numberWithBool:0];
+ let _: id =
+ msg_send![id, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
+ })?;
+
+ Ok(())
}
/// Set the inset of the traffic lights.
/// This will move the traffic lights to the specified position.
/// This is only available on macOS.
+ /// TODO: Also Implement for Windows 11 (>=22H2)
#[cfg(target_os = "macos")]
- fn set_traffic_lights_inset(
+ fn set_window_buttons_inset(
&self,
- inset: Option>,
- ) -> Result<&WebviewWindow, Error> {
- let insets_state = &self.state::();
- let mut insets_map = insets_state.0.write();
+ inset_option: Option>,
+ ) -> tauri::Result<()> {
+ let styles_state = &self.state::();
+ let mut styles_map = styles_state.0.write();
let window_label = self.label().to_string();
- match inset {
+ match inset_option {
Some(inset) => {
- if insets_map.insert(window_label, inset.clone()).is_none() {
+ if styles_map
+ .insert(window_label, Some(inset.clone()))
+ .is_none()
+ {
+ let c_insets_map = styles_map.clone();
+ let c_win = self.clone();
+
self.on_window_event(move |event| match event {
- tauri::WindowEvent::ThemeChanged(_) => {
- // TODO: Update
+ tauri::WindowEvent::ThemeChanged(..) => {
+ if c_insets_map.contains_key(c_win.label()) {
+ let _ = ensure_main_thread(&c_win, move |win| {
+ macos::update_window_controls_inset(&win.as_ref().window());
+ Ok(())
+ });
+ }
}
_ => (),
});
}
-
- ensure_main_thread(self, move |win| {
- // macos::update_traffic_lights_inset(win);
- Ok(win)
- })
}
None => {
- insets_map.remove(&window_label);
- Ok(self)
+ styles_map.remove(&window_label);
}
}
- }
- /// Set the window background to transparent.
- /// This helper function is different from Tauri's default
- /// as it doesn't use the `transparent` flag or macOS Private APIs.
- #[cfg(target_os = "macos")]
- fn make_transparent(&self) -> Result<&WebviewWindow, Error> {
- use cocoa::{
- base::{id, nil},
- foundation::NSString,
- };
+ ensure_main_thread(self, move |win| {
+ let inset = inset_option.unwrap_or(macos::DEFAULT_TRAFFIC_LIGHTS_INSET);
- // Make webview background transparent
- self.with_webview(|webview| unsafe {
- let id = webview.inner();
- let no: id = msg_send![class!(NSNumber), numberWithBool:0];
- let _: id =
- msg_send![id, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
- })?;
+ macos::draw_window_controls(
+ macos::nswindow_delegates::UnsafeWindowHandle(
+ win.ns_window().expect("Failed to create window handle"),
+ ),
+ inset.x,
+ inset.y,
+ );
- Ok(self)
+ Ok(())
+ })
}
- /// Set the window level.
+ /// Set the window level.
/// This will set the window level to the specified value.
/// NSWindowLevel values can be found [here](https://developer.apple.com/documentation/appkit/NSWindowLevel?language=objc).
/// This is only available on macOS.
#[cfg(target_os = "macos")]
- fn set_window_level(&self, level: NSWindowLevel) -> Result<&WebviewWindow, Error> {
- ensure_main_thread(self, move |win| {
+ fn set_window_level(&self, level: NSWindowLevel) -> tauri::Result<()> {
+ ensure_main_thread(self, move |win| unsafe {
let ns_win = win.ns_window()? as cocoa::base::id;
- unsafe {
- let _: () = msg_send![ns_win, setLevel: level];
- }
- Ok(win)
+ let _: () = msg_send![ns_win, setLevel: level];
+ Ok(())
})
}
}
-#[cfg(target_os = "macos")]
-struct TrafficLightsInsetsState(Arc>>>);
+#[cfg(not(target_os = "linux"))]
+struct WindowButtonsInsetsState(
+ Arc>>>>,
+);
pub fn init() -> TauriPlugin {
let mut builder = Builder::new("decorum")
- .invoke_handler(tauri::generate_handler![commands::show_snap_overlay])
+ .invoke_handler(tauri::generate_handler![
+ commands::set_window_buttons_inset,
+ commands::show_snap_overlay,
+ ])
.setup(move |app, _api| {
- #[cfg(target_os = "macos")]
- app.manage(TrafficLightsInsetsState(Arc::new(RwLock::new(
- HashMap::new(),
- ))));
+ #[cfg(not(target_os = "linux"))]
+ app.manage(WindowButtonsInsetsState(Arc::new(
+ parking_lot::RwLock::new(HashMap::new()),
+ )));
Ok(())
})
@@ -228,32 +252,18 @@ pub fn init() -> TauriPlugin {
}
#[cfg(target_os = "macos")]
-fn is_main_thread() -> bool {
- std::thread::current().name() == Some("main")
-}
-
-#[cfg(target_os = "macos")]
-fn ensure_main_thread(
- win: &WebviewWindow,
+pub(crate) fn ensure_main_thread(
+ win: &WebviewWindow,
main_action: F,
-) -> Result<&WebviewWindow, tauri::Error>
+) -> tauri::Result<()>
where
- F: FnOnce(&WebviewWindow) -> Result<&WebviewWindow, Error> + Send + 'static,
+ F: FnOnce(&WebviewWindow) -> tauri::Result<()> + Send + 'static,
{
- match is_main_thread() {
- true => {
- main_action(win)?;
- Ok(win)
- }
+ match std::thread::current().name() == Some("main") {
+ true => main_action(win),
false => {
- let win2 = win.clone();
-
- match win.run_on_main_thread(move || {
- main_action(&win2).unwrap();
- }) {
- Ok(_) => Ok(win),
- Err(e) => Err(e),
- }
+ let c_win = win.clone();
+ win.run_on_main_thread(move || main_action(&c_win).unwrap())
}
}
}
diff --git a/src/macos/mod.rs b/src/macos/mod.rs
index 54674f0..489d0ea 100644
--- a/src/macos/mod.rs
+++ b/src/macos/mod.rs
@@ -1,36 +1,36 @@
use nswindow_delegates::UnsafeWindowHandle;
-// use objc::{msg_send, sel, sel_impl};
-// use rand::{distributions::Alphanumeric, Rng};
use tauri::{LogicalPosition, Manager, Runtime, Window};
-use crate::TrafficLightsInsetsState;
+use crate::WindowButtonsInsetsState;
pub mod nswindow_delegates;
-const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition = LogicalPosition::new(12.0, 16.0);
+pub const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition = LogicalPosition::new(10.0, 15.0);
-pub fn update_traffic_lights_inset(window: &Window) {
- let insets_state = &window.state::();
- let insets_map = insets_state.0.read();
- let inset = insets_map
- .get(&window.label().to_string())
- .unwrap_or(&DEFAULT_TRAFFIC_LIGHTS_INSET);
+pub fn update_window_controls_inset(window: &Window) {
+ let styles_state = window.state::();
+ let styles_map_rw = styles_state.0.try_read();
+ if let Some(map) = styles_map_rw {
+ if let Some(inset_option) = map.get(&window.label().to_string()) {
+ let inset = inset_option.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET);
- position_traffic_lights(
- UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
- inset.x,
- inset.y,
- );
+ draw_window_controls(
+ UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
+ inset.x,
+ inset.y,
+ );
+ }
+ }
}
-fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
+pub fn draw_window_controls(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
+
let ns_window = ns_window_handle.0 as cocoa::base::id;
unsafe {
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
- let miniaturize =
- ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
+ let minimize = ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
@@ -44,14 +44,13 @@ fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64)
title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height;
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
- let window_buttons = vec![close, miniaturize, zoom];
- let space_between = 20.0; // Fixed space between buttons
+ let window_buttons = vec![close, minimize, zoom];
+ let space_between = NSView::frame(minimize).origin.x - NSView::frame(close).origin.x;
let vertical_offset = 4.0; // Adjust this value to push buttons down
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
- // Adjust vertical positioning
rect.origin.y = ((title_bar_frame_height - button_height) / 2.0) - vertical_offset;
button.setFrameOrigin(rect.origin);
}
diff --git a/src/macos/nswindow_delegates.rs b/src/macos/nswindow_delegates.rs
index d7c509c..67f0aa8 100644
--- a/src/macos/nswindow_delegates.rs
+++ b/src/macos/nswindow_delegates.rs
@@ -1,6 +1,5 @@
///
-/// Credit to @haasal, @charrondev and Hoppscotch
-/// This is also similar to how it is implemented in Zed.dev editor
+/// Credit to @haasal, @charrondev, Hoppscotch app, Electron, Zed Editor
///
/// https://github.com/haasal
/// https://gist.github.com/charrondev
@@ -9,6 +8,7 @@
/// (Issue) https://github.com/tauri-apps/tauri/issues/4789
/// (Gist) https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9
/// (Hoppscotch) https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
+/// (Electron) https://github.com/electron/electron/blob/38512efd25a159ddc64a54c22ef9eb6dd60064ec/shell/browser/native_window_mac.mm#L1454
///
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
@@ -30,15 +30,10 @@ pub fn setup(window: Window) {
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
- use crate::macos::update_traffic_lights_inset;
+ use crate::macos::update_window_controls_inset;
// Do the initial positioning
- // position_traffic_lights(
- // UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
- // WINDOW_CONTROL_PAD_X,
- // WINDOW_CONTROL_PAD_Y,
- // );
- // update_traffic_lights_inset(&window);
+ update_window_controls_inset(&window);
// Ensure they stay in place while resizing the window.
fn with_window_state) -> T, T>(
@@ -75,7 +70,7 @@ pub fn setup(window: Window) {
extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
unsafe {
with_window_state(&*this, |state: &mut WindowState| {
- update_traffic_lights_inset(&state.window);
+ update_window_controls_inset(&state.window);
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -201,7 +196,7 @@ pub fn setup(window: Window) {
.emit("did-exit-fullscreen", ())
.expect("Failed to emit event");
- update_traffic_lights_inset(&state.window);
+ update_window_controls_inset(&state.window);
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -259,7 +254,6 @@ pub fn setup(window: Window) {
}
}
- // Are we deallocing this properly ? (I miss safe Rust :( )
let window_label = window.label().to_string();
let app_state = WindowState { window };
@@ -272,7 +266,7 @@ pub fn setup(window: Window) {
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.
- let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
+ let delegate_name = format!("windowDelegate_decorum_{}_{}", window_label, random_str);
ns_win.setDelegate_(cocoa::delegate!(&delegate_name, {
window: id = ns_win,
diff --git a/yarn.lock b/yarn.lock
deleted file mode 100644
index 1e497e6..0000000
--- a/yarn.lock
+++ /dev/null
@@ -1,198 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@rollup/plugin-typescript@^11.1.6":
- version "11.1.6"
- resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz#724237d5ec12609ec01429f619d2a3e7d4d1b22b"
- integrity sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==
- dependencies:
- "@rollup/pluginutils" "^5.1.0"
- resolve "^1.22.1"
-
-"@rollup/pluginutils@^5.1.0":
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0"
- integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
- dependencies:
- "@types/estree" "^1.0.0"
- estree-walker "^2.0.2"
- picomatch "^2.3.1"
-
-"@rollup/rollup-android-arm-eabi@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz#1a32112822660ee104c5dd3a7c595e26100d4c2d"
- integrity sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==
-
-"@rollup/rollup-android-arm64@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz#5aeef206d65ff4db423f3a93f71af91b28662c5b"
- integrity sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==
-
-"@rollup/rollup-darwin-arm64@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz#6b66aaf003c70454c292cd5f0236ebdc6ffbdf1a"
- integrity sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==
-
-"@rollup/rollup-darwin-x64@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz#f64fc51ed12b19f883131ccbcea59fc68cbd6c0b"
- integrity sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz#1a7641111be67c10111f7122d1e375d1226cbf14"
- integrity sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==
-
-"@rollup/rollup-linux-arm-musleabihf@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz#c93fd632923e0fee25aacd2ae414288d0b7455bb"
- integrity sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==
-
-"@rollup/rollup-linux-arm64-gnu@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz#fa531425dd21d058a630947527b4612d9d0b4a4a"
- integrity sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==
-
-"@rollup/rollup-linux-arm64-musl@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz#8acc16f095ceea5854caf7b07e73f7d1802ac5af"
- integrity sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==
-
-"@rollup/rollup-linux-powerpc64le-gnu@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz#94e69a8499b5cf368911b83a44bb230782aeb571"
- integrity sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==
-
-"@rollup/rollup-linux-riscv64-gnu@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz#7ef1c781c7e59e85a6ce261cc95d7f1e0b56db0f"
- integrity sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==
-
-"@rollup/rollup-linux-s390x-gnu@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz#f15775841c3232fca9b78cd25a7a0512c694b354"
- integrity sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==
-
-"@rollup/rollup-linux-x64-gnu@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz#b521d271798d037ad70c9f85dd97d25f8a52e811"
- integrity sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==
-
-"@rollup/rollup-linux-x64-musl@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz#9254019cc4baac35800991315d133cc9fd1bf385"
- integrity sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==
-
-"@rollup/rollup-win32-arm64-msvc@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz#27f65a89f6f52ee9426ec11e3571038e4671790f"
- integrity sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==
-
-"@rollup/rollup-win32-ia32-msvc@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz#a2fbf8246ed0bb014f078ca34ae6b377a90cb411"
- integrity sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==
-
-"@rollup/rollup-win32-x64-msvc@4.17.2":
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
- integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
-
-"@tauri-apps/api@>=2.0.0-beta.24":
- version "2.0.0-beta.15"
- resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-beta.15.tgz#41d5fbeaccf3926b6f9f3f9fb5137ba2e272e5c1"
- integrity sha512-H9w6iISmR+NvH4XuyCZB4zDN10tf9RFt6i/9JHEjaRhAowdAaJ+oiXq/3kedizNClHMtbTQ5j0oqDVPkZDAI8g==
-
-"@types/estree@1.0.5", "@types/estree@^1.0.0":
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
- integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
-
-estree-walker@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
- integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-
-fsevents@~2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
- integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-
-function-bind@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
- integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
-
-hasown@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
- integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
- dependencies:
- function-bind "^1.1.2"
-
-is-core-module@^2.13.0:
- version "2.13.1"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
- integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
- dependencies:
- hasown "^2.0.0"
-
-path-parse@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
- integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-
-picomatch@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
- integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
-
-resolve@^1.22.1:
- version "1.22.8"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
- integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
- dependencies:
- is-core-module "^2.13.0"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
-
-rollup@^4.9.6:
- version "4.17.2"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.17.2.tgz#26d1785d0144122277fdb20ab3a24729ae68301f"
- integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==
- dependencies:
- "@types/estree" "1.0.5"
- optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.17.2"
- "@rollup/rollup-android-arm64" "4.17.2"
- "@rollup/rollup-darwin-arm64" "4.17.2"
- "@rollup/rollup-darwin-x64" "4.17.2"
- "@rollup/rollup-linux-arm-gnueabihf" "4.17.2"
- "@rollup/rollup-linux-arm-musleabihf" "4.17.2"
- "@rollup/rollup-linux-arm64-gnu" "4.17.2"
- "@rollup/rollup-linux-arm64-musl" "4.17.2"
- "@rollup/rollup-linux-powerpc64le-gnu" "4.17.2"
- "@rollup/rollup-linux-riscv64-gnu" "4.17.2"
- "@rollup/rollup-linux-s390x-gnu" "4.17.2"
- "@rollup/rollup-linux-x64-gnu" "4.17.2"
- "@rollup/rollup-linux-x64-musl" "4.17.2"
- "@rollup/rollup-win32-arm64-msvc" "4.17.2"
- "@rollup/rollup-win32-ia32-msvc" "4.17.2"
- "@rollup/rollup-win32-x64-msvc" "4.17.2"
- fsevents "~2.3.2"
-
-supports-preserve-symlinks-flag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
- integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-
-tslib@^2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
- integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
-
-typescript@^5.3.3:
- version "5.4.5"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
- integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
From 8425b9a842b07befc407dc50471cc4ea2d454754 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Thu, 29 Aug 2024 23:13:02 +0330
Subject: [PATCH 05/14] Update usage in README.md
---
README.md | 22 ++++++++++++-------
examples/tauri-app/src-tauri/src/main.rs | 4 ++++
permissions/autogenerated/reference.md | 28 +-----------------------
permissions/schemas/schema.json | 14 ------------
4 files changed, 19 insertions(+), 49 deletions(-)
diff --git a/README.md b/README.md
index 1eb16f9..ce2d9c9 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ You'll need to set these for your window in `src-tauri/capabilities/default.json
"core:window:allow-start-dragging",
"core:window:allow-toggle-maximize",
"decorum:allow-show-snap-overlay",
+"decorum:allow-set-window-buttons-inset",
```
And ensure the `withGlobalTauri` in your `tauri.conf.json` is set to `true`.
@@ -44,7 +45,7 @@ And ensure the `withGlobalTauri` in your `tauri.conf.json` is set to `true`.
```rust
use tauri::Manager;
-use tauri_plugin_decorum::WebviewWindowExt; // adds helper methods to WebviewWindow
+use tauri_plugin_decorum::{WebviewWindowExt, NSWindowLevel};
fn main() {
tauri::Builder::default()
@@ -58,15 +59,20 @@ fn main() {
// Some macOS-specific helpers
#[cfg(target_os = "macos")] {
- // Set a custom inset to the traffic lights
- main_window.set_traffic_lights_inset(12.0, 16.0).unwrap();
-
- // Make window transparent without privateApi
- main_window.make_transparent().unwrap()
-
+ use tauri_plugin_decorum::NSWindowLevel;
+
// Set window level
// NSWindowLevel: https://developer.apple.com/documentation/appkit/nswindowlevel
- main_window.set_window_level(25).unwrap()
+ let _ = main_window.set_window_level(NSWindowLevel::NSStatusWindowLevel);
+
+ // Set a custom inset to the traffic lights
+ let _ = main_window.set_window_buttons_inset(Some(LogicalPosition::new(12.0, 16.0)));
+
+ // Applies transparency to the webview.
+ // Note that this still uses internal/private APIs.
+ // This differs from Tauri's implementation as it doesn't make the window itself transparent
+ // Allowing for shadows to be enabled without artifacts or large performance hits.
+ let _ = main_window.apply_transparency();
}
Ok(())
diff --git a/examples/tauri-app/src-tauri/src/main.rs b/examples/tauri-app/src-tauri/src/main.rs
index 036e1a0..d671e76 100644
--- a/examples/tauri-app/src-tauri/src/main.rs
+++ b/examples/tauri-app/src-tauri/src/main.rs
@@ -16,10 +16,14 @@ fn main() {
#[cfg(target_os = "macos")]
{
+ use tauri_plugin_decorum::NSWindowLevel;
+
let _ = main_window.make_transparent();
let _ = main_window.create_overlay_titlebar();
let _ =
main_window.set_window_buttons_inset(Some(LogicalPosition::new(15.0, 25.0)));
+
+ let _ = main_window.set_window_level(NSWindowLevel::NSStatusWindowLevel);
}
Ok(())
diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md
index 53abc37..d9c8b72 100644
--- a/permissions/autogenerated/reference.md
+++ b/permissions/autogenerated/reference.md
@@ -1,5 +1,5 @@
-### Permission Table
+## Permission Table
@@ -8,32 +8,6 @@
-
-|
-
-`decorum:allow-set-window-buttons-inset`
-
- |
-
-
-Enables the set-window-buttons-inset command without any pre-configured scope.
-
- |
-
-
-
-|
-
-`decorum:deny-set-window-buttons-inset`
-
- |
-
-
-Denies the set-window-buttons-inset command without any pre-configured scope.
-
- |
-
-
|
diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json
index 7b4fcc2..e9eeae6 100644
--- a/permissions/schemas/schema.json
+++ b/permissions/schemas/schema.json
@@ -294,20 +294,6 @@
"PermissionKind": {
"type": "string",
"oneOf": [
- {
- "description": "allow-set-window-buttons-inset -> Enables the set-window-buttons-inset command without any pre-configured scope.",
- "type": "string",
- "enum": [
- "allow-set-window-buttons-inset"
- ]
- },
- {
- "description": "deny-set-window-buttons-inset -> Denies the set-window-buttons-inset command without any pre-configured scope.",
- "type": "string",
- "enum": [
- "deny-set-window-buttons-inset"
- ]
- },
{
"description": "allow-set-window-buttons-inset -> Enables the set_window_buttons_inset command without any pre-configured scope.",
"type": "string",
From d0a0ef5c6e513a9be74c1483a77907e4189d60e2 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Fri, 30 Aug 2024 03:02:19 +0330
Subject: [PATCH 06/14] from u32 for NSWindowLevel, update example
---
examples/tauri-app/src-tauri/src/main.rs | 4 ++--
src/lib.rs | 28 ++++++++++--------------
src/macos/mod.rs | 16 +++++++++-----
3 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/examples/tauri-app/src-tauri/src/main.rs b/examples/tauri-app/src-tauri/src/main.rs
index d671e76..91545a5 100644
--- a/examples/tauri-app/src-tauri/src/main.rs
+++ b/examples/tauri-app/src-tauri/src/main.rs
@@ -21,9 +21,9 @@ fn main() {
let _ = main_window.make_transparent();
let _ = main_window.create_overlay_titlebar();
let _ =
- main_window.set_window_buttons_inset(Some(LogicalPosition::new(15.0, 25.0)));
+ main_window.set_window_buttons_inset(Some(LogicalPosition::new(15.0, 20.0)));
- let _ = main_window.set_window_level(NSWindowLevel::NSStatusWindowLevel);
+ let _ = main_window.set_window_level(NSWindowLevel::NSNormalWindowLevel);
}
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index 2d51ff9..9969686 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,21 +27,17 @@ pub enum NSWindowLevel {
}
#[cfg(target_os = "macos")]
-impl From for NSWindowLevel {
- fn from(s: String) -> Self {
- match s.as_str() {
- "NSNormalWindowLevel" => NSWindowLevel::NSNormalWindowLevel,
- "NSFloatingWindowLevel" => NSWindowLevel::NSFloatingOrSubmenuOrTornOffMenuWindowLevel,
- "NSSubmenuWindowLevel" => NSWindowLevel::NSFloatingOrSubmenuOrTornOffMenuWindowLevel,
- "NSTornOffMenuWindowLevel" => {
- NSWindowLevel::NSFloatingOrSubmenuOrTornOffMenuWindowLevel
- }
- "NSMainMenuWindowLevel" => NSWindowLevel::NSMainMenuWindowLevel,
- "NSStatusWindowLevel" => NSWindowLevel::NSStatusWindowLevel,
- "NSModalPanelWindowLevel" => NSWindowLevel::NSModalPanelWindowLevel,
- "NSPopUpMenuWindowLevel" => NSWindowLevel::NSPopUpMenuWindowLevel,
- "NSScreenSaverWindowLevel" => NSWindowLevel::NSScreenSaverWindowLevel,
- _ => panic!("Unknown NSWindowLevel string: {}", s),
+impl From for NSWindowLevel {
+ fn from(n: u32) -> Self {
+ match n {
+ 0 => NSWindowLevel::NSNormalWindowLevel,
+ 3 => NSWindowLevel::NSFloatingOrSubmenuOrTornOffMenuWindowLevel,
+ 8 => NSWindowLevel::NSModalPanelWindowLevel,
+ 24 => NSWindowLevel::NSMainMenuWindowLevel,
+ 25 => NSWindowLevel::NSStatusWindowLevel,
+ 101 => NSWindowLevel::NSPopUpMenuWindowLevel,
+ 1000 => NSWindowLevel::NSScreenSaverWindowLevel,
+ _ => NSWindowLevel::NSNormalWindowLevel,
}
}
}
@@ -188,7 +184,7 @@ impl WebviewWindowExt for WebviewWindow {
ensure_main_thread(self, move |win| {
let inset = inset_option.unwrap_or(macos::DEFAULT_TRAFFIC_LIGHTS_INSET);
- macos::draw_window_controls(
+ macos::position_window_controls(
macos::nswindow_delegates::UnsafeWindowHandle(
win.ns_window().expect("Failed to create window handle"),
),
diff --git a/src/macos/mod.rs b/src/macos/mod.rs
index 489d0ea..ce31f6a 100644
--- a/src/macos/mod.rs
+++ b/src/macos/mod.rs
@@ -1,3 +1,4 @@
+use cocoa::appkit::NSToolbar;
use nswindow_delegates::UnsafeWindowHandle;
use tauri::{LogicalPosition, Manager, Runtime, Window};
@@ -14,7 +15,7 @@ pub fn update_window_controls_inset(window: &Window) {
if let Some(inset_option) = map.get(&window.label().to_string()) {
let inset = inset_option.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET);
- draw_window_controls(
+ position_window_controls(
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
inset.x,
inset.y,
@@ -23,11 +24,16 @@ pub fn update_window_controls_inset(window: &Window) {
}
}
-pub fn draw_window_controls(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
- use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
- use cocoa::foundation::NSRect;
+// TODO: Respect RTL display language
+// https://developer.apple.com/documentation/appkit/nsapplication/1428556-userinterfacelayoutdirection?language=objc
+pub fn position_window_controls(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
+ use cocoa::{
+ appkit::{NSView, NSWindow, NSWindowButton},
+ base::id,
+ foundation::NSRect,
+ };
- let ns_window = ns_window_handle.0 as cocoa::base::id;
+ let ns_window = ns_window_handle.0 as id;
unsafe {
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let minimize = ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
From 3d3da161db00bb25b5d96d1c72640f276ac87dea Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sat, 31 Aug 2024 03:58:36 +0330
Subject: [PATCH 07/14] Add more documentation
---
examples/tauri-app/src/styles.css | 2 +-
src/lib.rs | 101 +++++++++++++++++-------------
2 files changed, 57 insertions(+), 46 deletions(-)
diff --git a/examples/tauri-app/src/styles.css b/examples/tauri-app/src/styles.css
index 3df6665..d1bdf81 100644
--- a/examples/tauri-app/src/styles.css
+++ b/examples/tauri-app/src/styles.css
@@ -91,7 +91,7 @@ button {
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
- background-image: linear-gradient(180deg, #350042 0%, #1f003b 100%);
+ /* background-image: linear-gradient(180deg, #350042 0%, #1f003b 100%); */
background-size: cover;
}
diff --git a/src/lib.rs b/src/lib.rs
index 9969686..bfbcb31 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,9 +1,8 @@
use std::collections::HashMap;
use std::sync::Arc;
-use macos::nswindow_delegates;
use tauri::plugin::{Builder, TauriPlugin};
-use tauri::{Emitter, Listener, LogicalPosition, Manager, Runtime, WebviewWindow};
+use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
#[cfg(target_os = "macos")]
#[macro_use]
@@ -44,23 +43,26 @@ impl From for NSWindowLevel {
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the decorum APIs.
pub trait WebviewWindowExt {
- fn create_overlay_titlebar(&self) -> tauri::Result<()>;
+ fn create_overlay_titlebar(&self) -> Result<()>;
#[cfg(target_os = "macos")]
- fn set_window_buttons_inset(&self, options: Option>) -> tauri::Result<()>;
+ fn set_window_buttons_inset(&self, options: Option>) -> Result<()>;
#[cfg(target_os = "macos")]
- fn make_transparent(&self) -> tauri::Result<()>;
+ fn make_transparent(&self) -> Result<()>;
#[cfg(target_os = "macos")]
- fn set_window_level(&self, level: NSWindowLevel) -> tauri::Result<()>;
+ fn set_window_level(&self, level: NSWindowLevel) -> Result<()>;
}
impl WebviewWindowExt for WebviewWindow {
/// Create a custom titlebar overlay.
/// This will remove the default titlebar and create a draggable area for the titlebar.
- /// On Windows, it will also create custom window controls.
- fn create_overlay_titlebar(&self) -> tauri::Result<()> {
+ ///
+ /// ## Platform-specific:
+ ///
+ /// - **Windows:** On Windows, it will also create custom window controls.
+ fn create_overlay_titlebar(&self) -> Result<()> {
#[cfg(target_os = "windows")]
self.set_decorations(false)?;
@@ -119,36 +121,14 @@ impl WebviewWindowExt for WebviewWindow {
Ok(())
}
- /// Set the window background to transparent.
- /// This helper function is different from Tauri's default
- /// as it doesn't use the `transparent` flag or macOS Private APIs.
+ /// Sets the window controls (Traffic lights) inset
+ ///
+ /// ## Platform-specific:
+ ///
+ /// - **macOS:** Only supported on macOS.
+ // TODO: Also Implement for Windows 11 (>=22H2)
#[cfg(target_os = "macos")]
- fn make_transparent(&self) -> tauri::Result<()> {
- use cocoa::{
- base::{id, nil},
- foundation::NSString,
- };
-
- // Make webview background transparent
- self.with_webview(|webview| unsafe {
- let id = webview.inner();
- let no: id = msg_send![class!(NSNumber), numberWithBool:0];
- let _: id =
- msg_send![id, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
- })?;
-
- Ok(())
- }
-
- /// Set the inset of the traffic lights.
- /// This will move the traffic lights to the specified position.
- /// This is only available on macOS.
- /// TODO: Also Implement for Windows 11 (>=22H2)
- #[cfg(target_os = "macos")]
- fn set_window_buttons_inset(
- &self,
- inset_option: Option>,
- ) -> tauri::Result<()> {
+ fn set_window_buttons_inset(&self, inset_option: Option>) -> Result<()> {
let styles_state = &self.state::();
let mut styles_map = styles_state.0.write();
@@ -196,12 +176,43 @@ impl WebviewWindowExt for WebviewWindow {
})
}
+ /// Makes the background of the WKWebView layer transparent.
+ /// This differs from Tauri's implementation as it does not change the window background which causes performance performance issues and artifacts when shadows are enabled on the window.
+ /// Use Tauri's implementation to make the window itself transparent.
+ ///
+ /// ## Platform-specific:
+ ///
+ /// - **macOS:** Only supported on macOS.
+ /// This is a private API on macOS, so you cannot use this if your application will be published on the App Store.
+ #[cfg(target_os = "macos")]
+ fn make_transparent(&self) -> Result<()> {
+ use cocoa::{
+ base::{id, nil},
+ foundation::NSString,
+ };
+
+ self.with_webview(|webview| unsafe {
+ let wkwebview = webview.inner();
+ // `NO` is disallowed, use [NSNumber numberWithBool:NO]
+ let no: id = msg_send![class!(NSNumber), numberWithBool:0];
+ // Deprecated since OS X 10.14
+ // [https://developer.apple.com/documentation/webkit/webview/1408486-drawsbackground]
+ let _: id =
+ msg_send![wkwebview, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
+ })?;
+
+ Ok(())
+ }
+
/// Set the window level.
/// This will set the window level to the specified value.
- /// NSWindowLevel values can be found [here](https://developer.apple.com/documentation/appkit/NSWindowLevel?language=objc).
- /// This is only available on macOS.
+ /// NSWindowLevel values can be found [here](https://developer.apple.com/documentation/appkit/NSWindowLevel).
+ ///
+ /// ## Platform-specific:
+ ///
+ /// - **macOS:** Only supported on macOS.
#[cfg(target_os = "macos")]
- fn set_window_level(&self, level: NSWindowLevel) -> tauri::Result<()> {
+ fn set_window_level(&self, level: NSWindowLevel) -> Result<()> {
ensure_main_thread(self, move |win| unsafe {
let ns_win = win.ns_window()? as cocoa::base::id;
let _: () = msg_send![ns_win, setLevel: level];
@@ -210,7 +221,7 @@ impl WebviewWindowExt for WebviewWindow {
}
}
-#[cfg(not(target_os = "linux"))]
+#[cfg(any(target_os = "macos", target_os = "windows"))]
struct WindowButtonsInsetsState(
Arc>>>>,
);
@@ -222,7 +233,7 @@ pub fn init() -> TauriPlugin {
commands::show_snap_overlay,
])
.setup(move |app, _api| {
- #[cfg(not(target_os = "linux"))]
+ #[cfg(any(target_os = "macos", target_os = "windows"))]
app.manage(WindowButtonsInsetsState(Arc::new(
parking_lot::RwLock::new(HashMap::new()),
)));
@@ -240,7 +251,7 @@ pub fn init() -> TauriPlugin {
{
builder = builder.on_window_ready(|window| {
// TODO: Only setup if the inset is defined in the config.
- nswindow_delegates::setup(window);
+ macos::nswindow_delegates::setup(window);
});
}
@@ -251,9 +262,9 @@ pub fn init() -> TauriPlugin {
pub(crate) fn ensure_main_thread(
win: &WebviewWindow,
main_action: F,
-) -> tauri::Result<()>
+) -> Result<()>
where
- F: FnOnce(&WebviewWindow) -> tauri::Result<()> + Send + 'static,
+ F: FnOnce(&WebviewWindow) -> Result<()> + Send + 'static,
{
match std::thread::current().name() == Some("main") {
true => main_action(win),
From c179c33518f09a391931a631c9cfb2078889cd6b Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sat, 31 Aug 2024 10:11:52 +0330
Subject: [PATCH 08/14] Fix command error
---
src/commands.rs | 7 +++++--
src/lib.rs | 7 +++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/commands.rs b/src/commands.rs
index a1415b2..0bca148 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -1,4 +1,5 @@
-use tauri::{LogicalPosition, Manager, Result, Runtime, WebviewWindow};
+use anyhow::anyhow;
+use tauri::{Error, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
use crate::WebviewWindowExt;
@@ -41,5 +42,7 @@ pub async fn set_window_buttons_inset(
}
#[cfg(not(target_os = "macos"))]
- "This command is only supported on macOS."
+ Err(Error::Anyhow(anyhow!(
+ "This command is only supported on macOS."
+ )))
}
diff --git a/src/lib.rs b/src/lib.rs
index bfbcb31..29263e1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,9 @@
use std::collections::HashMap;
use std::sync::Arc;
+use anyhow::anyhow;
use tauri::plugin::{Builder, TauriPlugin};
-use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
+use tauri::{Emitter, Error, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
#[cfg(target_os = "macos")]
#[macro_use]
@@ -199,9 +200,7 @@ impl WebviewWindowExt for WebviewWindow {
// [https://developer.apple.com/documentation/webkit/webview/1408486-drawsbackground]
let _: id =
msg_send![wkwebview, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")];
- })?;
-
- Ok(())
+ })
}
/// Set the window level.
From 6090b46f5d05021aa95aace0c969d7f9e7799638 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sat, 31 Aug 2024 12:54:19 +0330
Subject: [PATCH 09/14] (WIP) Introduce: Decorum Config
---
Cargo.toml | 2 +-
src/config.rs | 91 +++++++++++++++++++++++++++++++++
src/lib.rs | 64 ++++++++++++++++++-----
src/macos/nswindow_delegates.rs | 22 ++++----
4 files changed, 153 insertions(+), 26 deletions(-)
create mode 100644 src/config.rs
diff --git a/Cargo.toml b/Cargo.toml
index bb6d620..838b381 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,12 +15,12 @@ repository = "https://github.com/clearlysid/tauri-plugin-decorum"
tauri = { version = "2.0.0-rc" }
serde = "1.0"
anyhow = "1.0"
+parking_lot = "0.12.3"
[target.'cfg(target_os = "macos")'.dependencies]
rand = "^0.8"
cocoa = "0.26"
objc = "0.2"
-parking_lot = "0.12.3"
[target.'cfg(target_os = "windows")'.dependencies]
enigo = "0.1.3"
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..13da056
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,91 @@
+use serde::{Deserialize, Serialize};
+
+///
+/// Configuration for windows in the application.
+///
+/// Default JSON configuration:
+/// ```json
+/// {
+/// "windows": [
+/// {
+/// "label": "main",
+/// "windowButtons": {
+/// "insetX": 15.0,
+/// "insetY": 20.0,
+/// "supportRTL": true,
+/// "hide": []
+/// },
+/// "transparentWebViews": true,
+/// "createOverlayTitlebar": true
+/// }
+/// ]
+/// }
+/// ```
+///
+/// # Note:
+/// - "hide" can include "zoom/maximize", "minimize", "close"
+/// - "transparentWebViews" can be a boolean or an array of string, each representing a webview label (e.g., ["main", "other_webview"])
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct DecorumConfig {
+ #[serde(default)]
+ pub windows: Vec,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct WindowConfig {
+ #[serde(default)]
+ pub label: String,
+ #[serde(default)]
+ pub window_buttons: Option,
+ #[serde(default)]
+ pub create_overlay_titlebar: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct WindowButtons {
+ #[serde(default)]
+ pub inset_x: Option,
+ #[serde(default)]
+ pub inset_y: Option,
+ #[serde(default)]
+ pub support_rtl: Option,
+ #[serde(default)]
+ pub hide: Option,
+ #[serde(default)]
+ pub transparent_webviews: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(untagged)]
+pub enum BoolOrVec {
+ Bool(bool),
+ Items(Vec),
+}
+
+impl Default for WindowConfig {
+ fn default() -> Self {
+ WindowConfig {
+ label: String::new(),
+ window_buttons: Some(WindowButtons::default()),
+ create_overlay_titlebar: Some(false),
+ }
+ }
+}
+
+impl Default for WindowButtons {
+ fn default() -> Self {
+ WindowButtons {
+ inset_x: Some(10.0),
+ inset_y: Some(15.0),
+ support_rtl: Some(false),
+ hide: Some(BoolOrVec::default()),
+ transparent_webviews: Some(BoolOrVec::default()),
+ }
+ }
+}
+
+impl Default for BoolOrVec {
+ fn default() -> Self {
+ BoolOrVec::Bool(false)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 29263e1..4465f6e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,15 +1,17 @@
use std::collections::HashMap;
use std::sync::Arc;
-use anyhow::anyhow;
+use config::{DecorumConfig, WindowConfig};
+use parking_lot::RwLock;
use tauri::plugin::{Builder, TauriPlugin};
-use tauri::{Emitter, Error, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
+use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
mod commands;
+mod config;
#[cfg(target_os = "macos")]
mod macos;
@@ -221,21 +223,51 @@ impl WebviewWindowExt for WebviewWindow {
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
-struct WindowButtonsInsetsState(
- Arc>>>>,
-);
+struct WindowButtonsInsetsState(Arc>>>>);
-pub fn init() -> TauriPlugin {
- let mut builder = Builder::new("decorum")
+#[allow(dead_code)]
+struct WindowConfigs(Arc>>);
+
+pub fn init() -> TauriPlugin {
+ let mut builder = Builder::::new("decorum")
.invoke_handler(tauri::generate_handler![
commands::set_window_buttons_inset,
commands::show_snap_overlay,
])
- .setup(move |app, _api| {
+ .setup(move |app, api| {
+ let window_configs: HashMap = api
+ .config()
+ .windows
+ .iter()
+ .map(|win_config| (win_config.label.clone(), win_config.clone()))
+ .collect();
+
+ let c_window_configs = window_configs.clone();
+ app.manage(WindowConfigs(Arc::new(RwLock::new(c_window_configs))));
+
#[cfg(any(target_os = "macos", target_os = "windows"))]
- app.manage(WindowButtonsInsetsState(Arc::new(
- parking_lot::RwLock::new(HashMap::new()),
- )));
+ {
+ let insets_map = window_configs
+ .iter()
+ .filter_map(|(label, config)| {
+ config.window_buttons.as_ref().and_then(|buttons| {
+ if buttons.inset_x.is_some() || buttons.inset_y.is_some() {
+ Some((
+ label.clone(),
+ Some(LogicalPosition::new(
+ buttons.inset_x.unwrap_or_default(),
+ buttons.inset_y.unwrap_or_default(),
+ )),
+ ))
+ } else {
+ None
+ }
+ })
+ })
+ .collect();
+
+ app.manage(WindowButtonsInsetsState(Arc::new(RwLock::new(insets_map))));
+ }
Ok(())
})
@@ -246,11 +278,15 @@ pub fn init() -> TauriPlugin {
}
});
- #[cfg(target_os = "macos")]
+ #[cfg(any(target_os = "macos", target_os = "windows"))]
{
builder = builder.on_window_ready(|window| {
- // TODO: Only setup if the inset is defined in the config.
- macos::nswindow_delegates::setup(window);
+ let insets_state = window.state::();
+ let insets_map = insets_state.0.read();
+
+ if let Some(Some(insets)) = insets_map.get(window.label()) {
+ macos::nswindow_delegates::setup(window.clone(), insets.to_owned());
+ }
});
}
diff --git a/src/macos/nswindow_delegates.rs b/src/macos/nswindow_delegates.rs
index 67f0aa8..663adff 100644
--- a/src/macos/nswindow_delegates.rs
+++ b/src/macos/nswindow_delegates.rs
@@ -12,7 +12,9 @@
///
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
-use tauri::{Emitter, Runtime, Window};
+use tauri::{Emitter, LogicalPosition, Runtime, Window};
+
+use crate::macos::position_window_controls;
pub struct UnsafeWindowHandle(pub *mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
@@ -23,7 +25,7 @@ struct WindowState {
window: Window,
}
-pub fn setup(window: Window) {
+pub fn setup(window: Window, initial: LogicalPosition) {
use cocoa::appkit::NSWindow;
use cocoa::base::{id, BOOL};
use cocoa::foundation::NSUInteger;
@@ -32,8 +34,10 @@ pub fn setup(window: Window) {
use crate::macos::update_window_controls_inset;
+ let ns_win = window.ns_window().expect("Failed to create window handle");
+
// Do the initial positioning
- update_window_controls_inset(&window);
+ position_window_controls(UnsafeWindowHandle(ns_win), initial.x, initial.y);
// Ensure they stay in place while resizing the window.
fn with_window_state) -> T, T>(
@@ -48,12 +52,8 @@ pub fn setup(window: Window) {
}
unsafe {
- let ns_win = window
- .ns_window()
- .expect("NS Window should exist to mount traffic light delegate.")
- as id;
-
- let current_delegate: id = ns_win.delegate();
+ let ns_win_id = ns_win as id;
+ let current_delegate: id = ns_win_id.delegate();
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
unsafe {
@@ -268,8 +268,8 @@ pub fn setup(window: Window) {
// delegate with the same name.
let delegate_name = format!("windowDelegate_decorum_{}_{}", window_label, random_str);
- ns_win.setDelegate_(cocoa::delegate!(&delegate_name, {
- window: id = ns_win,
+ ns_win_id.setDelegate_(cocoa::delegate!(&delegate_name, {
+ window: id = ns_win_id,
app_box: *mut c_void = app_box,
toolbar: id = cocoa::base::nil,
super_delegate: id = current_delegate,
From a42957808b17347df4a01956ee3613d8d1947571 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sat, 31 Aug 2024 13:14:42 +0330
Subject: [PATCH 10/14] config strucure change, small clean-ups
---
src/config.rs | 15 +++++++--------
src/lib.rs | 11 ++++++-----
src/macos/mod.rs | 1 -
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/src/config.rs b/src/config.rs
index 13da056..f095883 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
-///
/// Configuration for windows in the application.
///
/// Default JSON configuration:
@@ -16,7 +15,7 @@ use serde::{Deserialize, Serialize};
/// "hide": []
/// },
/// "transparentWebViews": true,
-/// "createOverlayTitlebar": true
+/// "createOverlayTitlebar": ["main"]
/// }
/// ]
/// }
@@ -24,7 +23,7 @@ use serde::{Deserialize, Serialize};
///
/// # Note:
/// - "hide" can include "zoom/maximize", "minimize", "close"
-/// - "transparentWebViews" can be a boolean or an array of string, each representing a webview label (e.g., ["main", "other_webview"])
+/// - "transparentWebViews" and "createOverlayTitlebar" can be a boolean or an array of string, each representing a webview label (e.g., ["main", "other_webview"])
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DecorumConfig {
#[serde(default)]
@@ -38,7 +37,9 @@ pub struct WindowConfig {
#[serde(default)]
pub window_buttons: Option,
#[serde(default)]
- pub create_overlay_titlebar: Option,
+ pub create_overlay_titlebar: Option,
+ #[serde(default)]
+ pub transparent_webviews: Option,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -51,8 +52,6 @@ pub struct WindowButtons {
pub support_rtl: Option,
#[serde(default)]
pub hide: Option,
- #[serde(default)]
- pub transparent_webviews: Option,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -67,7 +66,8 @@ impl Default for WindowConfig {
WindowConfig {
label: String::new(),
window_buttons: Some(WindowButtons::default()),
- create_overlay_titlebar: Some(false),
+ create_overlay_titlebar: Some(BoolOrVec::default()),
+ transparent_webviews: Some(BoolOrVec::default()),
}
}
}
@@ -79,7 +79,6 @@ impl Default for WindowButtons {
inset_y: Some(15.0),
support_rtl: Some(false),
hide: Some(BoolOrVec::default()),
- transparent_webviews: Some(BoolOrVec::default()),
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 4465f6e..c5c7bab 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -132,18 +132,18 @@ impl WebviewWindowExt for WebviewWindow {
// TODO: Also Implement for Windows 11 (>=22H2)
#[cfg(target_os = "macos")]
fn set_window_buttons_inset(&self, inset_option: Option>) -> Result<()> {
- let styles_state = &self.state::();
- let mut styles_map = styles_state.0.write();
+ let insets_state = &self.state::();
+ let mut insets_map = insets_state.0.write();
let window_label = self.label().to_string();
match inset_option {
Some(inset) => {
- if styles_map
+ if insets_map
.insert(window_label, Some(inset.clone()))
.is_none()
{
- let c_insets_map = styles_map.clone();
+ let c_insets_map = insets_map.clone();
let c_win = self.clone();
self.on_window_event(move |event| match event {
@@ -160,7 +160,7 @@ impl WebviewWindowExt for WebviewWindow {
}
}
None => {
- styles_map.remove(&window_label);
+ insets_map.remove(&window_label);
}
}
@@ -285,6 +285,7 @@ pub fn init() -> TauriPlugin {
let insets_map = insets_state.0.read();
if let Some(Some(insets)) = insets_map.get(window.label()) {
+ #[cfg(target_os = "macos")]
macos::nswindow_delegates::setup(window.clone(), insets.to_owned());
}
});
diff --git a/src/macos/mod.rs b/src/macos/mod.rs
index ce31f6a..4d0459e 100644
--- a/src/macos/mod.rs
+++ b/src/macos/mod.rs
@@ -1,4 +1,3 @@
-use cocoa::appkit::NSToolbar;
use nswindow_delegates::UnsafeWindowHandle;
use tauri::{LogicalPosition, Manager, Runtime, Window};
From a5876acdbb39f2c68a07b19024cdd7d9225b07e3 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sun, 1 Sep 2024 11:40:18 +0330
Subject: [PATCH 11/14] Improve plugin config, merge defaults with specifics
---
src/config.rs | 177 ++++++++++++++++++++++++++++++++++++++++++++++++--
src/lib.rs | 23 +++----
2 files changed, 181 insertions(+), 19 deletions(-)
diff --git a/src/config.rs b/src/config.rs
index f095883..d82e7fe 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,24 @@
+use std::collections::HashMap;
+
use serde::{Deserialize, Serialize};
/// Configuration for windows in the application.
///
-/// Default JSON configuration:
+/// JSON configuration example:
/// ```json
/// {
+/// "all": {
+/// {
+/// "windowButtons": {
+/// "insetX": 15.0,
+/// "insetY": 20.0,
+/// "supportRTL": true,
+/// "hide": []
+/// },
+/// "transparentWebViews": ["main"],
+/// "createOverlayTitlebar": ["onboarding"]
+/// }
+/// },
/// "windows": [
/// {
/// "label": "main",
@@ -20,6 +34,8 @@ use serde::{Deserialize, Serialize};
/// ]
/// }
/// ```
+/// The "all" config applies to all windows and only the specified webviews specified.
+///
///
/// # Note:
/// - "hide" can include "zoom/maximize", "minimize", "close"
@@ -27,13 +43,15 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DecorumConfig {
#[serde(default)]
- pub windows: Vec,
+ pub all: WindowConfig,
+ #[serde(default)]
+ pub windows: Vec,
+ #[serde(skip)]
+ pub merged: HashMap,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WindowConfig {
- #[serde(default)]
- pub label: String,
#[serde(default)]
pub window_buttons: Option,
#[serde(default)]
@@ -42,6 +60,13 @@ pub struct WindowConfig {
pub transparent_webviews: Option,
}
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct LabeledWindowConfig {
+ pub label: String,
+ #[serde(flatten)]
+ pub config: WindowConfig,
+}
+
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WindowButtons {
#[serde(default)]
@@ -64,7 +89,6 @@ pub enum BoolOrVec {
impl Default for WindowConfig {
fn default() -> Self {
WindowConfig {
- label: String::new(),
window_buttons: Some(WindowButtons::default()),
create_overlay_titlebar: Some(BoolOrVec::default()),
transparent_webviews: Some(BoolOrVec::default()),
@@ -88,3 +112,146 @@ impl Default for BoolOrVec {
BoolOrVec::Bool(false)
}
}
+
+impl DecorumConfig {
+ /// Creates a new `DecorumConfig` with the given configurations and merges them.
+ ///
+ /// This constructor immediately calls `merge_configurations()` to ensure
+ /// that the `merged` field is populated with the combined configurations.
+ ///
+ /// # Arguments
+ ///
+ /// * `all` - The default configuration applied to all windows
+ /// * `windows` - A vector of labeled window-specific configurations
+ ///
+ /// # Returns
+ ///
+ /// A new `DecorumConfig` instance with merged configurations
+ pub fn new(all: WindowConfig, windows: Vec) -> Self {
+ let mut config = DecorumConfig {
+ all,
+ windows,
+ merged: HashMap::new(),
+ };
+ config.merge_configurations();
+ config
+ }
+
+ /// Merges the default "all" configuration with individual window configurations.
+ ///
+ /// This method populates the `merged` field with configurations for each window,
+ /// combining window-specific settings with the default "all" settings.
+ /// Window-specific settings take priority over the default settings.
+ fn merge_configurations(&mut self) {
+ let default_config = self.all.clone();
+ let mut merged = HashMap::new();
+
+ for LabeledWindowConfig { label, config } in &self.windows {
+ let merged_config = WindowConfig {
+ window_buttons: config
+ .window_buttons
+ .clone()
+ .or_else(|| default_config.window_buttons.clone()),
+ create_overlay_titlebar: merge_bool_or_vec(
+ &config.create_overlay_titlebar,
+ &default_config.create_overlay_titlebar,
+ ),
+ transparent_webviews: merge_bool_or_vec(
+ &config.transparent_webviews,
+ &default_config.transparent_webviews,
+ ),
+ };
+ merged.insert(label.clone(), merged_config);
+ }
+
+ // Add default config for any window not explicitly defined
+ for label in self.get_all_window_labels() {
+ merged
+ .entry(label)
+ .or_insert_with(|| default_config.clone());
+ }
+
+ self.merged = merged;
+ }
+
+ /// Retrieves all unique window labels mentioned in the configuration.
+ ///
+ /// This method collects labels from both the "all" section (for `create_overlay_titlebar` and `transparent_webviews`)
+ /// and the individual window configurations.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `HashSet` containing all unique window labels.
+ pub fn get_all_window_labels(&self) -> std::collections::HashSet {
+ let mut labels = std::collections::HashSet::new();
+
+ // Collect labels from "all" section
+ if let Some(BoolOrVec::Items(items)) = &self.all.create_overlay_titlebar {
+ labels.extend(items.iter().cloned());
+ }
+ if let Some(BoolOrVec::Items(items)) = &self.all.transparent_webviews {
+ labels.extend(items.iter().cloned());
+ }
+
+ // Collect labels from individual window configs
+ labels.extend(self.windows.iter().map(|w| w.label.clone()));
+
+ labels
+ }
+
+ /// Retrieves the merged configuration for a specific window.
+ ///
+ /// # Arguments
+ ///
+ /// * `label` - The label of the window to retrieve the configuration for
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Option<&WindowConfig>` containing the merged configuration for the specified window,
+ /// or `None` if no configuration exists for the given label.
+ pub fn get_window_config(&self, label: &str) -> Option<&WindowConfig> {
+ self.merged.get(label)
+ }
+}
+
+/// Merges two `BoolOrVec` options, prioritizing the specific configuration over the default.
+///
+/// This is used to combine window-specific settings with the default "all" settings.
+///
+/// # Arguments
+///
+/// * `specific` - The `Option` from a specific window configuration
+/// * `default` - The `Option` from the default "all" configuration
+///
+/// # Returns
+///
+/// Returns an `Option` that represents the merged configuration:
+/// - If `specific` is `Some(BoolOrVec::Bool(true))`, it returns that.
+/// - If `specific` is `Some(BoolOrVec::Items(items))` with non-empty items, it returns that.
+/// - If `specific` is `None` and `default` is `Some`, it returns the `default` value.
+/// - Otherwise, it returns `None`.
+pub fn merge_bool_or_vec(
+ specific: &Option,
+ default: &Option,
+) -> Option {
+ match (specific, default) {
+ (Some(BoolOrVec::Bool(true)), _) => Some(BoolOrVec::Bool(true)),
+ (Some(BoolOrVec::Items(items)), _) if !items.is_empty() => {
+ Some(BoolOrVec::Items(items.clone()))
+ }
+ (None, Some(default_value)) => Some(default_value.clone()),
+ _ => None,
+ }
+}
+
+impl Default for DecorumConfig {
+ fn default() -> Self {
+ let mut config = DecorumConfig {
+ all: WindowConfig::default(),
+ windows: Vec::new(),
+ merged: HashMap::new(),
+ };
+ config.merge_configurations();
+ config
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index c5c7bab..0cd876b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::sync::Arc;
-use config::{DecorumConfig, WindowConfig};
+use config::DecorumConfig;
use parking_lot::RwLock;
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
@@ -225,8 +225,8 @@ impl WebviewWindowExt for WebviewWindow {
#[cfg(any(target_os = "macos", target_os = "windows"))]
struct WindowButtonsInsetsState(Arc>>>>);
-#[allow(dead_code)]
-struct WindowConfigs(Arc>>);
+// #[allow(dead_code)]
+// struct DecorumConfigState(DecorumConfig);
pub fn init() -> TauriPlugin {
let mut builder = Builder::::new("decorum")
@@ -235,21 +235,15 @@ pub fn init() -> TauriPlugin {
commands::show_snap_overlay,
])
.setup(move |app, api| {
- let window_configs: HashMap = api
- .config()
- .windows
- .iter()
- .map(|win_config| (win_config.label.clone(), win_config.clone()))
- .collect();
+ let decorum_config = api.config().clone();
- let c_window_configs = window_configs.clone();
- app.manage(WindowConfigs(Arc::new(RwLock::new(c_window_configs))));
-
- #[cfg(any(target_os = "macos", target_os = "windows"))]
+ #[cfg(target_os = "macos")]
{
- let insets_map = window_configs
+ let insets_map: HashMap>> = decorum_config
+ .merged
.iter()
.filter_map(|(label, config)| {
+ // Make sure there's at least one inset defined.
config.window_buttons.as_ref().and_then(|buttons| {
if buttons.inset_x.is_some() || buttons.inset_y.is_some() {
Some((
@@ -269,6 +263,7 @@ pub fn init() -> TauriPlugin {
app.manage(WindowButtonsInsetsState(Arc::new(RwLock::new(insets_map))));
}
+ // app.manage(DecorumConfigState(c_config));
Ok(())
})
.on_page_load(|win, _payload: &tauri::webview::PageLoadPayload| {
From cbd84d3459dbae03e239d701cf4d077f02306e14 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sun, 1 Sep 2024 13:29:01 +0330
Subject: [PATCH 12/14] Respect default insets,
---
examples/tauri-app/src-tauri/src/main.rs | 6 +-
examples/tauri-app/src-tauri/tauri.conf.json | 25 +++-
src/commands.rs | 5 +-
src/config.rs | 150 +++++++------------
src/lib.rs | 31 ++--
5 files changed, 104 insertions(+), 113 deletions(-)
diff --git a/examples/tauri-app/src-tauri/src/main.rs b/examples/tauri-app/src-tauri/src/main.rs
index 91545a5..d91859e 100644
--- a/examples/tauri-app/src-tauri/src/main.rs
+++ b/examples/tauri-app/src-tauri/src/main.rs
@@ -1,7 +1,7 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-use tauri::{LogicalPosition, Manager};
+use tauri::Manager;
use tauri_plugin_decorum::WebviewWindowExt;
fn main() {
@@ -20,8 +20,8 @@ fn main() {
let _ = main_window.make_transparent();
let _ = main_window.create_overlay_titlebar();
- let _ =
- main_window.set_window_buttons_inset(Some(LogicalPosition::new(15.0, 20.0)));
+ // let _ =
+ // main_window.set_window_buttons_inset(Some(LogicalPosition::new(15.0, 20.0)));
let _ = main_window.set_window_level(NSWindowLevel::NSNormalWindowLevel);
}
diff --git a/examples/tauri-app/src-tauri/tauri.conf.json b/examples/tauri-app/src-tauri/tauri.conf.json
index 3064605..baf639e 100644
--- a/examples/tauri-app/src-tauri/tauri.conf.json
+++ b/examples/tauri-app/src-tauri/tauri.conf.json
@@ -8,7 +8,30 @@
"beforeBuildCommand": "pnpm build",
"frontendDist": "../.output/public"
},
- "plugins": {},
+ "plugins": {
+ "decorum": {
+ "default": {
+ "windowButtons": {
+ "insetX": 20.0,
+ "insetY": 20.0
+ }
+ },
+ "windows": [
+ {
+ "label": "other",
+ "windowButtons": {
+ "insetX": 14.0
+ }
+ },
+ {
+ "label": "main",
+ "windowButtons": {
+ "insetY": 15.0
+ }
+ }
+ ]
+ }
+ },
"app": {
"windows": [
{
diff --git a/src/commands.rs b/src/commands.rs
index 0bca148..3b7adeb 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -1,5 +1,4 @@
-use anyhow::anyhow;
-use tauri::{Error, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
+use tauri::{LogicalPosition, Manager, Result, Runtime, WebviewWindow};
use crate::WebviewWindowExt;
@@ -42,7 +41,7 @@ pub async fn set_window_buttons_inset(
}
#[cfg(not(target_os = "macos"))]
- Err(Error::Anyhow(anyhow!(
+ Err(tauri::Error::Anyhow(anyhow::anyhow!(
"This command is only supported on macOS."
)))
}
diff --git a/src/config.rs b/src/config.rs
index d82e7fe..912417b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -46,8 +46,6 @@ pub struct DecorumConfig {
pub all: WindowConfig,
#[serde(default)]
pub windows: Vec,
- #[serde(skip)]
- pub merged: HashMap,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -113,105 +111,66 @@ impl Default for BoolOrVec {
}
}
-impl DecorumConfig {
- /// Creates a new `DecorumConfig` with the given configurations and merges them.
- ///
- /// This constructor immediately calls `merge_configurations()` to ensure
- /// that the `merged` field is populated with the combined configurations.
- ///
- /// # Arguments
- ///
- /// * `all` - The default configuration applied to all windows
- /// * `windows` - A vector of labeled window-specific configurations
- ///
- /// # Returns
- ///
- /// A new `DecorumConfig` instance with merged configurations
- pub fn new(all: WindowConfig, windows: Vec) -> Self {
- let mut config = DecorumConfig {
- all,
- windows,
- merged: HashMap::new(),
+/// Merges the default "all" configuration with individual window configurations.
+///
+/// This method populates the `merged` field with configurations for each window,
+/// combining window-specific settings with the default "all" settings.
+/// Window-specific settings take priority over the default settings.
+pub fn merge(config: DecorumConfig) -> HashMap {
+ let default_config = config.all.clone();
+ let mut merged = HashMap::new();
+
+ for LabeledWindowConfig { label, config } in &config.windows {
+ let merged_config = WindowConfig {
+ window_buttons: config
+ .window_buttons
+ .clone()
+ .or_else(|| default_config.window_buttons.clone()),
+ create_overlay_titlebar: merge_bool_or_vec(
+ &config.create_overlay_titlebar,
+ &default_config.create_overlay_titlebar,
+ ),
+ transparent_webviews: merge_bool_or_vec(
+ &config.transparent_webviews,
+ &default_config.transparent_webviews,
+ ),
};
- config.merge_configurations();
- config
+ merged.insert(label.clone(), merged_config);
}
- /// Merges the default "all" configuration with individual window configurations.
- ///
- /// This method populates the `merged` field with configurations for each window,
- /// combining window-specific settings with the default "all" settings.
- /// Window-specific settings take priority over the default settings.
- fn merge_configurations(&mut self) {
- let default_config = self.all.clone();
- let mut merged = HashMap::new();
-
- for LabeledWindowConfig { label, config } in &self.windows {
- let merged_config = WindowConfig {
- window_buttons: config
- .window_buttons
- .clone()
- .or_else(|| default_config.window_buttons.clone()),
- create_overlay_titlebar: merge_bool_or_vec(
- &config.create_overlay_titlebar,
- &default_config.create_overlay_titlebar,
- ),
- transparent_webviews: merge_bool_or_vec(
- &config.transparent_webviews,
- &default_config.transparent_webviews,
- ),
- };
- merged.insert(label.clone(), merged_config);
- }
-
- // Add default config for any window not explicitly defined
- for label in self.get_all_window_labels() {
- merged
- .entry(label)
- .or_insert_with(|| default_config.clone());
- }
-
- self.merged = merged;
+ // Add default config for any window not explicitly defined
+ for label in get_all_window_labels(config) {
+ merged
+ .entry(label)
+ .or_insert_with(|| default_config.clone());
}
- /// Retrieves all unique window labels mentioned in the configuration.
- ///
- /// This method collects labels from both the "all" section (for `create_overlay_titlebar` and `transparent_webviews`)
- /// and the individual window configurations.
- ///
- /// # Returns
- ///
- /// Returns a `HashSet` containing all unique window labels.
- pub fn get_all_window_labels(&self) -> std::collections::HashSet {
- let mut labels = std::collections::HashSet::new();
-
- // Collect labels from "all" section
- if let Some(BoolOrVec::Items(items)) = &self.all.create_overlay_titlebar {
- labels.extend(items.iter().cloned());
- }
- if let Some(BoolOrVec::Items(items)) = &self.all.transparent_webviews {
- labels.extend(items.iter().cloned());
- }
+ merged
+}
- // Collect labels from individual window configs
- labels.extend(self.windows.iter().map(|w| w.label.clone()));
+/// Retrieves all unique window labels mentioned in the configuration.
+///
+/// This method collects labels from both the "all" section (for `create_overlay_titlebar` and `transparent_webviews`)
+/// and the individual window configurations.
+///
+/// # Returns
+///
+/// Returns a `HashSet` containing all unique window labels.
+pub fn get_all_window_labels(config: DecorumConfig) -> std::collections::HashSet {
+ let mut labels = std::collections::HashSet::new();
- labels
+ // Collect labels from "all" section
+ if let Some(BoolOrVec::Items(items)) = &config.all.create_overlay_titlebar {
+ labels.extend(items.iter().cloned());
}
-
- /// Retrieves the merged configuration for a specific window.
- ///
- /// # Arguments
- ///
- /// * `label` - The label of the window to retrieve the configuration for
- ///
- /// # Returns
- ///
- /// Returns an `Option<&WindowConfig>` containing the merged configuration for the specified window,
- /// or `None` if no configuration exists for the given label.
- pub fn get_window_config(&self, label: &str) -> Option<&WindowConfig> {
- self.merged.get(label)
+ if let Some(BoolOrVec::Items(items)) = &config.all.transparent_webviews {
+ labels.extend(items.iter().cloned());
}
+
+ // Collect labels from individual window configs
+ labels.extend(config.windows.iter().map(|w| w.label.clone()));
+
+ labels
}
/// Merges two `BoolOrVec` options, prioritizing the specific configuration over the default.
@@ -246,12 +205,9 @@ pub fn merge_bool_or_vec(
impl Default for DecorumConfig {
fn default() -> Self {
- let mut config = DecorumConfig {
+ DecorumConfig {
all: WindowConfig::default(),
windows: Vec::new(),
- merged: HashMap::new(),
- };
- config.merge_configurations();
- config
+ }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 0cd876b..e00483c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::sync::Arc;
-use config::DecorumConfig;
use parking_lot::RwLock;
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
@@ -225,22 +224,36 @@ impl WebviewWindowExt for WebviewWindow {
#[cfg(any(target_os = "macos", target_os = "windows"))]
struct WindowButtonsInsetsState(Arc>>>>);
-// #[allow(dead_code)]
-// struct DecorumConfigState(DecorumConfig);
+#[cfg(any(target_os = "macos", target_os = "windows"))]
+struct DefaultInsets(LogicalPosition);
+
+struct DecorumConfigState(HashMap);
-pub fn init() -> TauriPlugin {
- let mut builder = Builder::::new("decorum")
+pub fn init() -> TauriPlugin {
+ let mut builder = Builder::::new("decorum")
.invoke_handler(tauri::generate_handler![
commands::set_window_buttons_inset,
commands::show_snap_overlay,
])
.setup(move |app, api| {
- let decorum_config = api.config().clone();
+ let config = api.config().clone();
+
+ #[cfg(any(target_os = "macos", target_os = "windows"))]
+ if let Some(buttons) = &config.all.window_buttons {
+ app.manage(DefaultInsets(match (buttons.inset_x, buttons.inset_y) {
+ (Some(x), Some(y)) => LogicalPosition::new(x, y),
+ _ => macos::DEFAULT_TRAFFIC_LIGHTS_INSET,
+ }));
+ }
+
+ let merged = config::merge(config);
+
+ println!("-CONFIG: \n{:#?}", api.config().clone());
+ println!("-MERGED CONFIG: \n{:#?}", merged);
#[cfg(target_os = "macos")]
{
- let insets_map: HashMap>> = decorum_config
- .merged
+ let insets_map: HashMap>> = merged
.iter()
.filter_map(|(label, config)| {
// Make sure there's at least one inset defined.
@@ -263,7 +276,7 @@ pub fn init() -> TauriPlugin {
app.manage(WindowButtonsInsetsState(Arc::new(RwLock::new(insets_map))));
}
- // app.manage(DecorumConfigState(c_config));
+ app.manage(DecorumConfigState(merged));
Ok(())
})
.on_page_load(|win, _payload: &tauri::webview::PageLoadPayload| {
From 8d66e30fcf7e2fed9a8cc605953f99e98db34fb3 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Sun, 1 Sep 2024 20:26:12 +0330
Subject: [PATCH 13/14] Use a better merge method. New option
---
Cargo.toml | 1 +
examples/tauri-app/src-tauri/tauri.conf.json | 12 +-
src/config.rs | 216 +++++++++----------
src/lib.rs | 75 +++++--
src/macos/mod.rs | 4 +-
5 files changed, 165 insertions(+), 143 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index 838b381..baff079 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ tauri = { version = "2.0.0-rc" }
serde = "1.0"
anyhow = "1.0"
parking_lot = "0.12.3"
+serde_json = "1.0.127"
[target.'cfg(target_os = "macos")'.dependencies]
rand = "^0.8"
diff --git a/examples/tauri-app/src-tauri/tauri.conf.json b/examples/tauri-app/src-tauri/tauri.conf.json
index baf639e..dffd818 100644
--- a/examples/tauri-app/src-tauri/tauri.conf.json
+++ b/examples/tauri-app/src-tauri/tauri.conf.json
@@ -10,19 +10,13 @@
},
"plugins": {
"decorum": {
- "default": {
+ "all": {
+ "createOverlayTitlebar": true,
"windowButtons": {
- "insetX": 20.0,
- "insetY": 20.0
+ "insetX": 12.0
}
},
"windows": [
- {
- "label": "other",
- "windowButtons": {
- "insetX": 14.0
- }
- },
{
"label": "main",
"windowButtons": {
diff --git a/src/config.rs b/src/config.rs
index 912417b..269aecf 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,7 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
+use serde_json::Value;
/// Configuration for windows in the application.
///
@@ -8,16 +9,15 @@ use serde::{Deserialize, Serialize};
/// ```json
/// {
/// "all": {
-/// {
-/// "windowButtons": {
-/// "insetX": 15.0,
-/// "insetY": 20.0,
-/// "supportRTL": true,
-/// "hide": []
-/// },
-/// "transparentWebViews": ["main"],
-/// "createOverlayTitlebar": ["onboarding"]
-/// }
+/// "windowButtons": {
+/// "insetX": 15.0,
+/// "insetY": 20.0,
+/// "supportRTL": true,
+/// "hide": [],
+/// },
+/// "clickCloseToHide": true
+/// "transparentWebViews": ["main"],
+/// "createOverlayTitlebar": ["onboarding"]
/// },
/// "windows": [
/// {
@@ -28,8 +28,9 @@ use serde::{Deserialize, Serialize};
/// "supportRTL": true,
/// "hide": []
/// },
+/// "clickCloseToHide": false
/// "transparentWebViews": true,
-/// "createOverlayTitlebar": ["main"]
+/// "createOverlayTitlebar": ["main"],
/// }
/// ]
/// }
@@ -38,10 +39,11 @@ use serde::{Deserialize, Serialize};
///
///
/// # Note:
+/// - "clickCloseToHide" (macOS Only) hides the window instead of quitting
/// - "hide" can include "zoom/maximize", "minimize", "close"
-/// - "transparentWebViews" and "createOverlayTitlebar" can be a boolean or an array of string, each representing a webview label (e.g., ["main", "other_webview"])
+/// - "transparentWebViews" and "createOverlayTitlebar" can be a boolean or an array of string, each representing a webview label (e.g., "main", "other_webview")
#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct DecorumConfig {
+pub struct DecorumPluginConfig {
#[serde(default)]
pub all: WindowConfig,
#[serde(default)]
@@ -49,6 +51,7 @@ pub struct DecorumConfig {
}
#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
pub struct WindowConfig {
#[serde(default)]
pub window_buttons: Option,
@@ -56,6 +59,8 @@ pub struct WindowConfig {
pub create_overlay_titlebar: Option,
#[serde(default)]
pub transparent_webviews: Option,
+ #[serde(default)]
+ pub click_close_to_hide: Option,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -66,6 +71,7 @@ pub struct LabeledWindowConfig {
}
#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
pub struct WindowButtons {
#[serde(default)]
pub inset_x: Option,
@@ -87,127 +93,111 @@ pub enum BoolOrVec {
impl Default for WindowConfig {
fn default() -> Self {
WindowConfig {
- window_buttons: Some(WindowButtons::default()),
- create_overlay_titlebar: Some(BoolOrVec::default()),
- transparent_webviews: Some(BoolOrVec::default()),
+ window_buttons: None,
+ create_overlay_titlebar: None,
+ transparent_webviews: None,
+ click_close_to_hide: None,
}
}
}
-impl Default for WindowButtons {
+impl Default for DecorumPluginConfig {
fn default() -> Self {
- WindowButtons {
- inset_x: Some(10.0),
- inset_y: Some(15.0),
- support_rtl: Some(false),
- hide: Some(BoolOrVec::default()),
+ DecorumPluginConfig {
+ all: WindowConfig::default(),
+ windows: Vec::new(),
}
}
}
-impl Default for BoolOrVec {
- fn default() -> Self {
- BoolOrVec::Bool(false)
- }
-}
+impl DecorumPluginConfig {
+ /// Merges the default "all" configuration with individual window configurations.
+ ///
+ /// This method populates the `merged` field with configurations for each window,
+ /// combining window-specific settings with the default "all" settings.
+ /// Window-specific settings take priority over the default settings.
+ pub fn merged(&self) -> HashMap {
+ let mut merged = HashMap::new();
+
+ for LabeledWindowConfig { label, config } in &self.windows {
+ match merge(&self.all, config) {
+ Ok(config) => {
+ merged.insert(label.clone(), config);
+ }
+ Err(err) => {
+ eprintln!("Plugin Decorum - Skipping configs for window \"{}\" due to failure during merge: {}", label, err);
+ }
+ };
+ }
-/// Merges the default "all" configuration with individual window configurations.
-///
-/// This method populates the `merged` field with configurations for each window,
-/// combining window-specific settings with the default "all" settings.
-/// Window-specific settings take priority over the default settings.
-pub fn merge(config: DecorumConfig) -> HashMap {
- let default_config = config.all.clone();
- let mut merged = HashMap::new();
-
- for LabeledWindowConfig { label, config } in &config.windows {
- let merged_config = WindowConfig {
- window_buttons: config
- .window_buttons
- .clone()
- .or_else(|| default_config.window_buttons.clone()),
- create_overlay_titlebar: merge_bool_or_vec(
- &config.create_overlay_titlebar,
- &default_config.create_overlay_titlebar,
- ),
- transparent_webviews: merge_bool_or_vec(
- &config.transparent_webviews,
- &default_config.transparent_webviews,
- ),
- };
- merged.insert(label.clone(), merged_config);
- }
+ // Add default config for any window not explicitly defined
+ for label in self.get_all_window_labels() {
+ merged.entry(label).or_insert_with(|| self.all.clone());
+ }
- // Add default config for any window not explicitly defined
- for label in get_all_window_labels(config) {
merged
- .entry(label)
- .or_insert_with(|| default_config.clone());
}
- merged
-}
-
-/// Retrieves all unique window labels mentioned in the configuration.
-///
-/// This method collects labels from both the "all" section (for `create_overlay_titlebar` and `transparent_webviews`)
-/// and the individual window configurations.
-///
-/// # Returns
-///
-/// Returns a `HashSet` containing all unique window labels.
-pub fn get_all_window_labels(config: DecorumConfig) -> std::collections::HashSet {
- let mut labels = std::collections::HashSet::new();
+ /// Retrieves all unique window labels mentioned in the configuration.
+ ///
+ /// This method collects labels from both the "all" section (for `create_overlay_titlebar` and `transparent_webviews`)
+ /// and the individual window configurations.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `HashSet` containing all unique window labels.
+ pub fn get_all_window_labels(&self) -> HashSet {
+ let mut labels = HashSet::new();
+
+ if let Some(BoolOrVec::Items(items)) = &self.all.create_overlay_titlebar {
+ labels.extend(items.iter().cloned());
+ }
+ if let Some(BoolOrVec::Items(items)) = &self.all.transparent_webviews {
+ labels.extend(items.iter().cloned());
+ }
- // Collect labels from "all" section
- if let Some(BoolOrVec::Items(items)) = &config.all.create_overlay_titlebar {
- labels.extend(items.iter().cloned());
- }
- if let Some(BoolOrVec::Items(items)) = &config.all.transparent_webviews {
- labels.extend(items.iter().cloned());
+ labels.extend(self.windows.iter().map(|w| w.label.clone()));
+ labels
}
+}
- // Collect labels from individual window configs
- labels.extend(config.windows.iter().map(|w| w.label.clone()));
+// Credits: https://github.com/jondot/merge-struct/
+fn to_value(value: &T) -> Result {
+ serde_json::to_value(value)
+}
- labels
+fn from_value(
+ value: serde_json::Value,
+) -> Result {
+ serde_json::from_value(value)
}
-/// Merges two `BoolOrVec` options, prioritizing the specific configuration over the default.
-///
-/// This is used to combine window-specific settings with the default "all" settings.
-///
-/// # Arguments
-///
-/// * `specific` - The `Option` from a specific window configuration
-/// * `default` - The `Option` from the default "all" configuration
-///
-/// # Returns
-///
-/// Returns an `Option` that represents the merged configuration:
-/// - If `specific` is `Some(BoolOrVec::Bool(true))`, it returns that.
-/// - If `specific` is `Some(BoolOrVec::Items(items))` with non-empty items, it returns that.
-/// - If `specific` is `None` and `default` is `Some`, it returns the `default` value.
-/// - Otherwise, it returns `None`.
-pub fn merge_bool_or_vec(
- specific: &Option,
- default: &Option,
-) -> Option {
- match (specific, default) {
- (Some(BoolOrVec::Bool(true)), _) => Some(BoolOrVec::Bool(true)),
- (Some(BoolOrVec::Items(items)), _) if !items.is_empty() => {
- Some(BoolOrVec::Items(items.clone()))
- }
- (None, Some(default_value)) => Some(default_value.clone()),
- _ => None,
- }
+fn merge(
+ base: &T,
+ overrides: &T,
+) -> Result {
+ let mut left = to_value(base)?;
+ let right = to_value(overrides)?;
+ merge_value(&mut left, &right);
+ from_value(left)
}
-impl Default for DecorumConfig {
- fn default() -> Self {
- DecorumConfig {
- all: WindowConfig::default(),
- windows: Vec::new(),
+fn merge_value(a: &mut Value, b: &Value) {
+ match (a, b) {
+ (Value::Object(ref mut a), &Value::Object(ref b)) => {
+ for (k, v) in b {
+ merge_value(a.entry(k).or_insert(Value::Null), v);
+ }
+ }
+ (Value::Array(ref mut a), &Value::Array(ref b)) => {
+ a.extend(b.clone());
+ }
+ (Value::Array(ref mut a), &Value::Object(ref b)) => {
+ a.extend([Value::Object(b.clone())]);
+ }
+ (_, Value::Null) => {} // do nothing
+ (a, b) => {
+ *a = b.clone();
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index e00483c..bac0c02 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::sync::Arc;
+use config::{DecorumPluginConfig, WindowConfig};
use parking_lot::RwLock;
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Emitter, Listener, LogicalPosition, Manager, Result, Runtime, WebviewWindow};
@@ -131,7 +132,7 @@ impl WebviewWindowExt for WebviewWindow {
// TODO: Also Implement for Windows 11 (>=22H2)
#[cfg(target_os = "macos")]
fn set_window_buttons_inset(&self, inset_option: Option>) -> Result<()> {
- let insets_state = &self.state::();
+ let insets_state = &self.state::();
let mut insets_map = insets_state.0.write();
let window_label = self.label().to_string();
@@ -222,15 +223,34 @@ impl WebviewWindowExt for WebviewWindow {
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
-struct WindowButtonsInsetsState(Arc>>>>);
+struct WindowButtonsInsetState(Arc>>>>);
#[cfg(any(target_os = "macos", target_os = "windows"))]
-struct DefaultInsets(LogicalPosition);
+struct DefaultInset(Option>);
-struct DecorumConfigState(HashMap);
+struct DecorumConfigState {
+ all: WindowConfig,
+ merged: HashMap,
+}
+
+impl DecorumConfigState {
+ /// Retrieves the merged configuration for a specific window.
+ ///
+ /// # Arguments
+ ///
+ /// * `label` - The label of the window to retrieve the configuration for
+ ///
+ /// # Returns
+ ///
+ /// Returns an `Option<&WindowConfig>` containing the merged configuration for the specified window,
+ /// or `None` if no configuration exists for the given label.
+ pub fn get_for(&self, label: &str) -> Option<&WindowConfig> {
+ self.merged.get(label)
+ }
+}
-pub fn init() -> TauriPlugin {
- let mut builder = Builder::::new("decorum")
+pub fn init() -> TauriPlugin {
+ let mut builder = Builder::::new("decorum")
.invoke_handler(tauri::generate_handler![
commands::set_window_buttons_inset,
commands::show_snap_overlay,
@@ -239,21 +259,32 @@ pub fn init() -> TauriPlugin {
let config = api.config().clone();
#[cfg(any(target_os = "macos", target_os = "windows"))]
- if let Some(buttons) = &config.all.window_buttons {
- app.manage(DefaultInsets(match (buttons.inset_x, buttons.inset_y) {
- (Some(x), Some(y)) => LogicalPosition::new(x, y),
- _ => macos::DEFAULT_TRAFFIC_LIGHTS_INSET,
- }));
+ {
+ let default_inset = config.all.window_buttons.as_ref().and_then(|buttons| {
+ if buttons.inset_x.is_some() || buttons.inset_y.is_some() {
+ Some(LogicalPosition::new(
+ buttons.inset_x.unwrap_or_default(),
+ buttons.inset_y.unwrap_or_default(),
+ ))
+ } else {
+ None
+ }
+ });
+
+ app.manage(DefaultInset(default_inset));
}
- let merged = config::merge(config);
+ let merged_config = config.merged();
- println!("-CONFIG: \n{:#?}", api.config().clone());
- println!("-MERGED CONFIG: \n{:#?}", merged);
+ #[cfg(debug_assertions)]
+ {
+ println!("DECORUM-CONFIG: \n{:#?}", config.clone());
+ println!("DECORUM-MERGED CONFIG: \n{:#?}", merged_config);
+ }
#[cfg(target_os = "macos")]
{
- let insets_map: HashMap>> = merged
+ let insets_map: HashMap>> = merged_config
.iter()
.filter_map(|(label, config)| {
// Make sure there's at least one inset defined.
@@ -273,10 +304,13 @@ pub fn init() -> TauriPlugin {
})
.collect();
- app.manage(WindowButtonsInsetsState(Arc::new(RwLock::new(insets_map))));
+ app.manage(WindowButtonsInsetState(Arc::new(RwLock::new(insets_map))));
}
- app.manage(DecorumConfigState(merged));
+ app.manage(DecorumConfigState {
+ all: config.all,
+ merged: merged_config,
+ });
Ok(())
})
.on_page_load(|win, _payload: &tauri::webview::PageLoadPayload| {
@@ -289,10 +323,13 @@ pub fn init() -> TauriPlugin {
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
builder = builder.on_window_ready(|window| {
- let insets_state = window.state::();
+ let insets_state = window.state::();
let insets_map = insets_state.0.read();
- if let Some(Some(insets)) = insets_map.get(window.label()) {
+ if let Some(insets) = insets_map
+ .get(window.label())
+ .unwrap_or(&window.state::().0)
+ {
#[cfg(target_os = "macos")]
macos::nswindow_delegates::setup(window.clone(), insets.to_owned());
}
diff --git a/src/macos/mod.rs b/src/macos/mod.rs
index 4d0459e..57efe55 100644
--- a/src/macos/mod.rs
+++ b/src/macos/mod.rs
@@ -1,14 +1,14 @@
use nswindow_delegates::UnsafeWindowHandle;
use tauri::{LogicalPosition, Manager, Runtime, Window};
-use crate::WindowButtonsInsetsState;
+use crate::WindowButtonsInsetState;
pub mod nswindow_delegates;
pub const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition = LogicalPosition::new(10.0, 15.0);
pub fn update_window_controls_inset(window: &Window) {
- let styles_state = window.state::();
+ let styles_state = window.state::();
let styles_map_rw = styles_state.0.try_read();
if let Some(map) = styles_map_rw {
if let Some(inset_option) = map.get(&window.label().to_string()) {
From 76e84324b53bc7034a7687c5c7b20e2804598c44 Mon Sep 17 00:00:00 2001
From: Ilya <47112191+ItsEeleeya@users.noreply.github.com>
Date: Mon, 2 Sep 2024 09:51:58 +0330
Subject: [PATCH 14/14] Add set_window_level command
---
guest-js/index.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++-
src/commands.rs | 24 ++++++++++++++++
src/lib.rs | 7 ++++-
src/macos/mod.rs | 1 +
4 files changed, 102 insertions(+), 2 deletions(-)
diff --git a/guest-js/index.ts b/guest-js/index.ts
index a0cfe5d..8821d65 100644
--- a/guest-js/index.ts
+++ b/guest-js/index.ts
@@ -1,16 +1,86 @@
import { invoke } from "@tauri-apps/api/core";
import { LogicalPosition } from "@tauri-apps/api/dpi";
+import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
+import { Window } from "@tauri-apps/api/window";
export async function show_snap_overlay() {
await invoke("plugin:decorum|show_snap_overlay");
}
+/**
+ * Sets the window controls inset.
+ *
+ * ## Platform-specific:
+ *
+ * - **macOS:** Only supported on macOS.
+ *
+ * @param inset - The inset position for the window buttons.
+ * @param target - The target window. Can be a window label (string), a Window object, or null for the current window.
+ * @returns Promise
+ */
export async function setWindowButtonsInset(
inset: LogicalPosition | null,
- targetLabel: string | null = null
+ target: string | Window | null = null
) {
+ let targetLabel: string | null = null;
+
+ if (typeof target === "string") {
+ targetLabel = target;
+ } else if (target instanceof Window) {
+ targetLabel = target.label;
+ }
+
await invoke("plugin:decorum|set_window_buttons_inset", {
inset,
target_label: targetLabel,
+ }).catch((e) => {
+ console.error("Failed to set window buttons inset:", e);
+ });
+}
+
+/**
+ * Representation of [NSWindowLevel](https://developer.apple.com/documentation/appkit/NSWindowLevel)
+ */
+export enum NSWindowLevel {
+ NSNormalWindowLevel = 0,
+ NSFloatingWindowLevel = 3,
+ NSSubmenuWindowLevel = 3,
+ NSTornOffMenuWindowLevel = 3,
+ NSMainMenuWindowLevel = 24,
+ NSStatusWindowLevel = 25,
+ NSModalPanelWindowLevel = 8,
+ NSPopUpMenuWindowLevel = 101,
+ NSScreenSaverWindowLevel = 1000,
+}
+
+/**
+ * Set the window level.
+ * This will set the window level to the specified value.
+ *
+ * ## Platform-specific:
+ *
+ * - **macOS:** Only supported on macOS.
+ *
+ * @see {@link NSWindowLevel} for the available window levels.
+ * @param level - The window level to set.
+ * @returns Promise
+ */
+export async function setWindowLevel(
+ level: NSWindowLevel,
+ target: string | Window | null = null
+) {
+ let targetLabel: string | null = null;
+
+ if (typeof target === "string") {
+ targetLabel = target;
+ } else if (target instanceof WebviewWindow || target instanceof Window) {
+ targetLabel = target.label;
+ }
+
+ await invoke("plugin:decorum|set_window_level", {
+ level,
+ target_label: targetLabel,
+ }).catch((e) => {
+ console.error("Failed to set window level:", e);
});
}
diff --git a/src/commands.rs b/src/commands.rs
index 3b7adeb..292e659 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -45,3 +45,27 @@ pub async fn set_window_buttons_inset(
"This command is only supported on macOS."
)))
}
+
+#[tauri::command]
+pub async fn set_window_level(
+ window: WebviewWindow,
+ level: u32,
+ target_label: Option,
+) -> Result<()> {
+ #[cfg(target_os = "macos")]
+ {
+ let target = match target_label {
+ Some(label) => window
+ .get_webview_window(&label)
+ .ok_or_else(|| tauri::Error::WindowNotFound)?,
+ None => window,
+ };
+
+ target.set_window_level(crate::NSWindowLevel::from(level))
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ Err(tauri::Error::Anyhow(anyhow::anyhow!(
+ "This command is only supported on macOS."
+ )))
+}
diff --git a/src/lib.rs b/src/lib.rs
index bac0c02..8613b7e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,7 @@ mod config;
#[cfg(target_os = "macos")]
mod macos;
+/// Rust representation of [NSWindowLevel](https://developer.apple.com/documentation/appkit/NSWindowLevel)
#[cfg(target_os = "macos")]
#[repr(u32)]
pub enum NSWindowLevel {
@@ -186,7 +187,10 @@ impl | |