fix(tauri): restore desktop menu controls and fullscreen shortcut (#226)
## Summary - restore the missing desktop View and Window menu controls - use native reload and window actions where supported instead of brittle webview-only behavior - restore the working fullscreen keyboard shortcut while keeping the zoom menu labels aligned with the intended desktop behavior ## Testing - cargo check --manifest-path packages/tauri-app/src-tauri/Cargo.toml
This commit is contained in:
67
packages/tauri-app/Cargo.lock
generated
67
packages/tauri-app/Cargo.lock
generated
@@ -473,6 +473,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-notification",
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
@@ -1350,6 +1351,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -1482,6 +1493,24 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "global-hotkey"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"keyboard-types",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"x11rb",
|
||||||
|
"xkeysym",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -4055,6 +4084,21 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-global-shortcut"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
||||||
|
dependencies = [
|
||||||
|
"global-hotkey",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-notification"
|
name = "tauri-plugin-notification"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
@@ -5735,6 +5779,29 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xkeysym"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ keepawake = "0.6"
|
|||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
url = "2"
|
url = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2378,6 +2378,72 @@
|
|||||||
"const": "dialog:deny-save",
|
"const": "dialog:deny-save",
|
||||||
"markdownDescription": "Denies the save command without any pre-configured scope."
|
"markdownDescription": "Denies the save command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:default",
|
||||||
|
"markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the is_registered command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:allow-is-registered",
|
||||||
|
"markdownDescription": "Enables the is_registered command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:allow-register",
|
||||||
|
"markdownDescription": "Enables the register command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the register_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:allow-register-all",
|
||||||
|
"markdownDescription": "Enables the register_all command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the unregister command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:allow-unregister",
|
||||||
|
"markdownDescription": "Enables the unregister command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the unregister_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:allow-unregister-all",
|
||||||
|
"markdownDescription": "Enables the unregister_all command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the is_registered command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:deny-is-registered",
|
||||||
|
"markdownDescription": "Denies the is_registered command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:deny-register",
|
||||||
|
"markdownDescription": "Denies the register command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the register_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:deny-register-all",
|
||||||
|
"markdownDescription": "Denies the register_all command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the unregister command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:deny-unregister",
|
||||||
|
"markdownDescription": "Denies the unregister command without any pre-configured scope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the unregister_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "global-shortcut:deny-unregister-all",
|
||||||
|
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ use serde::Deserialize;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
|
||||||
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
|
||||||
use tauri::webview::Webview;
|
use tauri::webview::Webview;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Wry};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, WindowEvent, Wry};
|
||||||
|
use tauri_plugin_global_shortcut::{
|
||||||
|
Code as ShortcutCode, GlobalShortcutExt, Shortcut, ShortcutState,
|
||||||
|
};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -25,6 +29,10 @@ use std::os::windows::ffi::OsStrExt;
|
|||||||
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
||||||
|
|
||||||
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
const DEFAULT_ZOOM_LEVEL: f64 = 1.0;
|
||||||
|
const ZOOM_STEP: f64 = 0.2;
|
||||||
|
const MIN_ZOOM_LEVEL: f64 = 0.2;
|
||||||
|
const MAX_ZOOM_LEVEL: f64 = 5.0;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
||||||
@@ -32,6 +40,7 @@ const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub manager: CliProcessManager,
|
pub manager: CliProcessManager,
|
||||||
pub wake_lock: Mutex<Option<KeepAwake>>,
|
pub wake_lock: Mutex<Option<KeepAwake>>,
|
||||||
|
pub zoom_level: Mutex<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@@ -157,6 +166,83 @@ fn emit_folder_drop_event(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clamp_zoom_level(value: f64) -> f64 {
|
||||||
|
value.clamp(MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_main_window_zoom(app_handle: &AppHandle, next_zoom: f64) {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let normalized = clamp_zoom_level(next_zoom);
|
||||||
|
if window.set_zoom(normalized).is_ok() {
|
||||||
|
if let Ok(mut zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||||
|
*zoom_level = normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_main_window(app_handle: &AppHandle) {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_reload_main_window(app_handle: &AppHandle) {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
if let Ok(mut url) = window.url() {
|
||||||
|
if should_allow_internal(&url) {
|
||||||
|
let reload_token = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_millis()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let existing_pairs: Vec<(String, String)> = url
|
||||||
|
.query_pairs()
|
||||||
|
.into_owned()
|
||||||
|
.filter(|(key, _)| key != "__codenomad_force_reload")
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut pairs = url.query_pairs_mut();
|
||||||
|
pairs.clear();
|
||||||
|
for (key, value) in existing_pairs {
|
||||||
|
pairs.append_pair(&key, &value);
|
||||||
|
}
|
||||||
|
pairs.append_pair("__codenomad_force_reload", &reload_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = window.navigate(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = window.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_fullscreen_window(app_handle: &AppHandle) {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let next_fullscreen = !window.is_fullscreen().unwrap_or(false);
|
||||||
|
let _ = window.set_fullscreen(next_fullscreen);
|
||||||
|
if cfg!(not(target_os = "macos")) {
|
||||||
|
if next_fullscreen {
|
||||||
|
let _ = window.hide_menu();
|
||||||
|
} else {
|
||||||
|
let _ = window.show_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fullscreen_shortcut() -> Option<Shortcut> {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Shortcut::new(None, ShortcutCode::F11))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn set_windows_app_user_model_id() {
|
fn set_windows_app_user_model_id() {
|
||||||
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
let app_id: Vec<u16> = OsStr::new(WINDOWS_APP_USER_MODEL_ID)
|
||||||
@@ -181,15 +267,48 @@ fn main() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(
|
||||||
|
tauri_plugin_global_shortcut::Builder::new()
|
||||||
|
.with_handler(|app, shortcut, event| {
|
||||||
|
if event.state() != ShortcutState::Pressed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullscreen_shortcut().as_ref() == Some(shortcut) {
|
||||||
|
toggle_fullscreen_window(app);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(navigation_guard)
|
.plugin(navigation_guard)
|
||||||
.manage(AppState {
|
.manage(AppState {
|
||||||
manager: CliProcessManager::new(),
|
manager: CliProcessManager::new(),
|
||||||
wake_lock: Mutex::new(None),
|
wake_lock: Mutex::new(None),
|
||||||
|
zoom_level: Mutex::new(DEFAULT_ZOOM_LEVEL),
|
||||||
})
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
set_windows_app_user_model_id();
|
set_windows_app_user_model_id();
|
||||||
build_menu(&app.handle())?;
|
build_menu(&app.handle())?;
|
||||||
|
if let Some(shortcut) = fullscreen_shortcut() {
|
||||||
|
let shortcut_manager = app.handle().global_shortcut();
|
||||||
|
let _ = shortcut_manager.register(shortcut.clone());
|
||||||
|
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
window.on_window_event(move |event| {
|
||||||
|
if let WindowEvent::Focused(focused) = event {
|
||||||
|
let shortcut_manager = app_handle.global_shortcut();
|
||||||
|
if *focused {
|
||||||
|
let _ = shortcut_manager.register(shortcut.clone());
|
||||||
|
} else {
|
||||||
|
let _ = shortcut_manager.unregister(shortcut.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let dev_mode = is_dev_mode();
|
let dev_mode = is_dev_mode();
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
let manager = app.state::<AppState>().manager.clone();
|
let manager = app.state::<AppState>().manager.clone();
|
||||||
@@ -214,36 +333,42 @@ fn main() {
|
|||||||
let _ = window.emit("menu:newInstance", ());
|
let _ = window.emit("menu:newInstance", ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"close" => {
|
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
|
||||||
let _ = window.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"quit" => {
|
"quit" => {
|
||||||
app_handle.exit(0);
|
app_handle.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// View menu
|
// View menu
|
||||||
"reload" => {
|
"reload" => {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
reload_main_window(app_handle);
|
||||||
let _ = window.eval("window.location.reload()");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"force_reload" => {
|
"force_reload" => {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
force_reload_main_window(app_handle);
|
||||||
let _ = window.eval("window.location.reload(true)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"toggle_devtools" => {
|
"toggle_devtools" => {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
window.open_devtools();
|
if window.is_devtools_open() {
|
||||||
|
window.close_devtools();
|
||||||
|
} else {
|
||||||
|
window.open_devtools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"reset_zoom" => {
|
||||||
|
set_main_window_zoom(app_handle, DEFAULT_ZOOM_LEVEL);
|
||||||
|
}
|
||||||
|
"zoom_in" => {
|
||||||
|
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||||
|
set_main_window_zoom(app_handle, *zoom_level + ZOOM_STEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"zoom_out" => {
|
||||||
|
if let Ok(zoom_level) = app_handle.state::<AppState>().zoom_level.lock() {
|
||||||
|
set_main_window_zoom(app_handle, *zoom_level - ZOOM_STEP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"toggle_fullscreen" => {
|
"toggle_fullscreen" => {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
toggle_fullscreen_window(app_handle);
|
||||||
let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window menu
|
// Window menu
|
||||||
@@ -257,6 +382,11 @@ fn main() {
|
|||||||
let _ = window.maximize();
|
let _ = window.maximize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"close_window" => {
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// App menu (macOS)
|
// App menu (macOS)
|
||||||
"about" => {
|
"about" => {
|
||||||
@@ -344,6 +474,7 @@ fn main() {
|
|||||||
|
|
||||||
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
||||||
let is_mac = cfg!(target_os = "macos");
|
let is_mac = cfg!(target_os = "macos");
|
||||||
|
let is_linux = cfg!(target_os = "linux");
|
||||||
|
|
||||||
// Create submenus
|
// Create submenus
|
||||||
let mut submenus = Vec::new();
|
let mut submenus = Vec::new();
|
||||||
@@ -371,16 +502,74 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
|||||||
Some("CmdOrCtrl+N"),
|
Some("CmdOrCtrl+N"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let file_menu = SubmenuBuilder::new(app, "File")
|
let file_menu = if is_mac {
|
||||||
.item(&new_instance_item)
|
SubmenuBuilder::new(app, "File")
|
||||||
.separator()
|
.item(&new_instance_item)
|
||||||
.text(
|
.separator()
|
||||||
if is_mac { "close" } else { "quit" },
|
.close_window()
|
||||||
if is_mac { "Close" } else { "Quit" },
|
.build()?
|
||||||
)
|
} else {
|
||||||
.build()?;
|
SubmenuBuilder::new(app, "File")
|
||||||
|
.item(&new_instance_item)
|
||||||
|
.separator()
|
||||||
|
.text("quit", "Quit")
|
||||||
|
.build()?
|
||||||
|
};
|
||||||
submenus.push(file_menu);
|
submenus.push(file_menu);
|
||||||
|
|
||||||
|
let reload_item = MenuItem::with_id(app, "reload", "Reload", true, Some("CmdOrCtrl+R"))?;
|
||||||
|
let force_reload_item = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"force_reload",
|
||||||
|
"Force Reload",
|
||||||
|
true,
|
||||||
|
Some("CmdOrCtrl+Shift+R"),
|
||||||
|
)?;
|
||||||
|
let toggle_devtools_item = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"toggle_devtools",
|
||||||
|
"Toggle Developer Tools",
|
||||||
|
true,
|
||||||
|
Some("Alt+CmdOrCtrl+I"),
|
||||||
|
)?;
|
||||||
|
let reset_zoom_item =
|
||||||
|
MenuItem::with_id(app, "reset_zoom", "Actual Size", true, Some("CmdOrCtrl+0"))?;
|
||||||
|
let zoom_in_item = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"zoom_in",
|
||||||
|
if is_mac { "Zoom In" } else { "Zoom In\tCtrl++" },
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
let zoom_out_item = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"zoom_out",
|
||||||
|
if is_mac {
|
||||||
|
"Zoom Out"
|
||||||
|
} else {
|
||||||
|
"Zoom Out\tCtrl+-"
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
let toggle_fullscreen_item = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
"toggle_fullscreen",
|
||||||
|
if is_mac {
|
||||||
|
"Toggle Full Screen"
|
||||||
|
} else {
|
||||||
|
"Toggle Full Screen\tF11"
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
if is_mac {
|
||||||
|
Some("Ctrl+Cmd+F")
|
||||||
|
} else {
|
||||||
|
None::<&str>
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let close_window_item =
|
||||||
|
MenuItem::with_id(app, "close_window", "Close", true, Some("CmdOrCtrl+W"))?;
|
||||||
|
|
||||||
// Edit menu with predefined items for standard functionality
|
// Edit menu with predefined items for standard functionality
|
||||||
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
||||||
.undo()
|
.undo()
|
||||||
@@ -396,20 +585,39 @@ fn build_menu(app: &AppHandle) -> tauri::Result<()> {
|
|||||||
|
|
||||||
// View menu
|
// View menu
|
||||||
let view_menu = SubmenuBuilder::new(app, "View")
|
let view_menu = SubmenuBuilder::new(app, "View")
|
||||||
.text("reload", "Reload")
|
.item(&reload_item)
|
||||||
.text("force_reload", "Force Reload")
|
.item(&force_reload_item)
|
||||||
.text("toggle_devtools", "Toggle Developer Tools")
|
.item(&toggle_devtools_item)
|
||||||
.separator()
|
.separator()
|
||||||
|
.item(&reset_zoom_item)
|
||||||
|
.item(&zoom_in_item)
|
||||||
|
.item(&zoom_out_item)
|
||||||
.separator()
|
.separator()
|
||||||
.text("toggle_fullscreen", "Toggle Full Screen")
|
.item(&toggle_fullscreen_item)
|
||||||
.build()?;
|
.build()?;
|
||||||
submenus.push(view_menu);
|
submenus.push(view_menu);
|
||||||
|
|
||||||
// Window menu
|
// Window menu
|
||||||
let window_menu = SubmenuBuilder::new(app, "Window")
|
let window_menu = if is_linux {
|
||||||
.text("minimize", "Minimize")
|
SubmenuBuilder::new(app, "Window")
|
||||||
.text("zoom", "Zoom")
|
.text("minimize", "Minimize")
|
||||||
.build()?;
|
.text("zoom", "Zoom")
|
||||||
|
.separator()
|
||||||
|
.item(&close_window_item)
|
||||||
|
.build()?
|
||||||
|
} else if is_mac {
|
||||||
|
SubmenuBuilder::new(app, "Window")
|
||||||
|
.minimize()
|
||||||
|
.maximize()
|
||||||
|
.build()?
|
||||||
|
} else {
|
||||||
|
SubmenuBuilder::new(app, "Window")
|
||||||
|
.minimize()
|
||||||
|
.maximize()
|
||||||
|
.separator()
|
||||||
|
.close_window()
|
||||||
|
.build()?
|
||||||
|
};
|
||||||
submenus.push(window_menu);
|
submenus.push(window_menu);
|
||||||
|
|
||||||
// Build the main menu with all submenus
|
// Build the main menu with all submenus
|
||||||
|
|||||||
Reference in New Issue
Block a user