feat(desktop): 添加悬浮球 Quick Ask 功能
- 实现浅灰色玻璃拟态悬浮球,带机器人头图标 - 支持点击展开对话框,淡入淡出动画 - 支持窗口拖拽,区分点击和拖拽操作 - macOS 透明窗口支持 (macOSPrivateApi) - 悬浮球 hover 放大效果,不溢出窗口 - 添加系统托盘 Toggle Quick Ask 菜单
This commit is contained in:
@@ -14,7 +14,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["tray-icon"] }
|
||||
tauri = { version = "2", features = ["macos-private-api", "tray-icon"] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
"windows": ["*"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-is-visible",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"shell:default",
|
||||
"fs:default",
|
||||
"dialog:default",
|
||||
|
||||
@@ -2,6 +2,7 @@ use serde::Serialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -116,3 +117,70 @@ pub async fn list_directory(path: String) -> Result<Vec<DirectoryEntry>, String>
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
// 悬浮窗口控制命令
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn toggle_floating_window(app: tauri::AppHandle) -> Result<bool, String> {
|
||||
if let Some(window) = app.get_webview_window("floating") {
|
||||
let is_visible = window.is_visible().map_err(|e| e.to_string())?;
|
||||
if is_visible {
|
||||
window.hide().map_err(|e| e.to_string())?;
|
||||
Ok(false)
|
||||
} else {
|
||||
window.show().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
Ok(true)
|
||||
}
|
||||
} else {
|
||||
Err("Floating window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn show_floating_window(app: tauri::AppHandle) -> Result<(), String> {
|
||||
if let Some(window) = app.get_webview_window("floating") {
|
||||
window.show().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Floating window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hide_floating_window(app: tauri::AppHandle) -> Result<(), String> {
|
||||
if let Some(window) = app.get_webview_window("floating") {
|
||||
window.hide().map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Floating window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window.show().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Main window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_floating_window_size(
|
||||
app: tauri::AppHandle,
|
||||
width: f64,
|
||||
height: f64,
|
||||
) -> Result<(), String> {
|
||||
if let Some(window) = app.get_webview_window("floating") {
|
||||
window
|
||||
.set_size(tauri::Size::Logical(tauri::LogicalSize { width, height }))
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Floating window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,22 @@ pub fn run() {
|
||||
commands::open_directory_dialog,
|
||||
commands::read_local_file,
|
||||
commands::list_directory,
|
||||
commands::toggle_floating_window,
|
||||
commands::show_floating_window,
|
||||
commands::hide_floating_window,
|
||||
commands::show_main_window,
|
||||
commands::set_floating_window_size,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn setup_tray<R: Runtime>(app: &tauri::App<R>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let show_item = MenuItem::with_id(app, "show", "Show Window", true, None::<&str>)?;
|
||||
let show_item = MenuItem::with_id(app, "show", "Show Main Window", true, None::<&str>)?;
|
||||
let floating_item = MenuItem::with_id(app, "floating", "Toggle Quick Ask", true, None::<&str>)?;
|
||||
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::with_items(app, &[&show_item, &quit_item])?;
|
||||
let menu = Menu::with_items(app, &[&show_item, &floating_item, &quit_item])?;
|
||||
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.menu(&menu)
|
||||
@@ -42,6 +48,16 @@ fn setup_tray<R: Runtime>(app: &tauri::App<R>) -> Result<(), Box<dyn std::error:
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"floating" => {
|
||||
if let Some(window) = app.get_webview_window("floating") {
|
||||
if window.is_visible().unwrap_or(false) {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"macOSPrivateApi": true,
|
||||
"windows": [
|
||||
{
|
||||
"label": "main",
|
||||
"title": "AI Assistant",
|
||||
"width": 1200,
|
||||
"height": 800,
|
||||
@@ -21,13 +23,29 @@
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"center": true
|
||||
},
|
||||
{
|
||||
"label": "floating",
|
||||
"title": "",
|
||||
"url": "/floating.html",
|
||||
"width": 60,
|
||||
"height": 60,
|
||||
"minWidth": 60,
|
||||
"minHeight": 60,
|
||||
"resizable": false,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"shadow": false,
|
||||
"alwaysOnTop": true,
|
||||
"visible": true,
|
||||
"skipTaskbar": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src 'self'; connect-src 'self' http://localhost:* ws://localhost:* http://127.0.0.1:* ws://127.0.0.1:*; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
|
||||
},
|
||||
"trayIcon": {
|
||||
"iconPath": "icons/icon.png",
|
||||
"iconPath": "icons/32x32.png",
|
||||
"iconAsTemplate": true
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user