性能优化
This commit is contained in:
@@ -17,9 +17,10 @@ use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||
use tauri::window::{Effect, EffectsBuilder};
|
||||
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState};
|
||||
|
||||
use crate::clipboard::popup::{get_cursor_pos, get_work_area_at_point, get_dpi_for_point};
|
||||
use crate::win32_util::{get_cursor_pos, get_work_area_at_point, get_dpi_for_point};
|
||||
|
||||
use specta::Type;
|
||||
|
||||
/// 弹窗窗口标签
|
||||
pub const POPUP_LABEL: &str = "quick-panel";
|
||||
@@ -28,15 +29,16 @@ pub const POPUP_LABEL: &str = "quick-panel";
|
||||
const WIN_W: f64 = 600.0;
|
||||
const WIN_H: f64 = 420.0;
|
||||
|
||||
/// 当前注册的快捷键(用于注销旧快捷键)
|
||||
static CURRENT_SHORTCUT: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
/// 标志:show_popup 兜底创建路径设为 true,前端 onMounted 回调 show_window 时据此判断是否显示。
|
||||
/// 预创建路径不设置,避免应用启动时弹窗自动弹出。
|
||||
static POPUP_PENDING_SHOW: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// 兜底创建路径下 show_popup 计算出的待显示位置(物理坐标),供 show_window 应用,
|
||||
/// 避免窗口重建后仍停留在屏幕外 (-10000, -10000)。
|
||||
static PENDING_POS: Mutex<Option<(f64, f64)>> = Mutex::new(None);
|
||||
|
||||
/// 自定义命令
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CustomCommand {
|
||||
pub id: String,
|
||||
@@ -47,7 +49,7 @@ pub struct CustomCommand {
|
||||
}
|
||||
|
||||
/// 快速面板设置
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QuickPanelSettings {
|
||||
/// 全局快捷键(如 "Alt+Space"),空字符串表示不注册。
|
||||
@@ -131,56 +133,6 @@ pub fn save_settings(app: &AppHandle, settings: &QuickPanelSettings) -> Result<(
|
||||
std::fs::write(&path, json).map_err(|e| format!("写入设置文件失败: {}", e))
|
||||
}
|
||||
|
||||
/// 解析快捷键字符串为 Shortcut(格式如 "Alt+Space"、"Ctrl+Shift+P")
|
||||
/// 失败返回 None。
|
||||
pub fn parse_shortcut(s: &str) -> Option<Shortcut> {
|
||||
s.trim().parse::<Shortcut>().ok()
|
||||
}
|
||||
|
||||
/// 注册全局快捷键。重复调用会先注销旧快捷键。
|
||||
/// 传入空字符串则仅注销不注册。
|
||||
pub fn register_shortcut(app: &AppHandle, shortcut_str: &str) -> Result<(), String> {
|
||||
// 先注销旧快捷键
|
||||
unregister_shortcut(app);
|
||||
|
||||
if shortcut_str.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shortcut = parse_shortcut(shortcut_str)
|
||||
.ok_or_else(|| format!("无效的快捷键: {}", shortcut_str))?;
|
||||
|
||||
let app_handle = app.clone();
|
||||
app.global_shortcut()
|
||||
.on_shortcut(shortcut, move |_app, _shortcut, event| {
|
||||
// 仅在按下时触发(松开不触发)
|
||||
if event.state == ShortcutState::Pressed {
|
||||
show_popup(&app_handle);
|
||||
}
|
||||
})
|
||||
.map_err(|e| format!("注册快捷键失败: {}", e))?;
|
||||
|
||||
if let Ok(mut cur) = CURRENT_SHORTCUT.lock() {
|
||||
*cur = Some(shortcut_str.to_string());
|
||||
}
|
||||
eprintln!("[quickpanel] 已注册快捷键: {}", shortcut_str);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 注销当前快捷键
|
||||
pub fn unregister_shortcut(app: &AppHandle) {
|
||||
if let Ok(cur) = CURRENT_SHORTCUT.lock() {
|
||||
if let Some(ref s) = *cur {
|
||||
if let Some(shortcut) = parse_shortcut(s) {
|
||||
let _ = app.global_shortcut().unregister(shortcut);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut cur) = CURRENT_SHORTCUT.lock() {
|
||||
*cur = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建弹窗窗口(隐藏状态)并注册失焦监听。
|
||||
/// 位置默认在屏幕外,show_popup 时会重新定位到鼠标所在显示器中央。
|
||||
/// 预创建后首次按快捷键走"窗口已存在"分支直接 show,避免首次创建的时序问题。
|
||||
@@ -206,7 +158,7 @@ fn create_popup_window(app: &AppHandle) {
|
||||
{
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("[quickpanel] 创建弹窗失败: {}", e);
|
||||
crate::logger::log_error("quickpanel", &format!("创建弹窗失败: {}", e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -217,11 +169,11 @@ fn create_popup_window(app: &AppHandle) {
|
||||
win.on_window_event(move |event| {
|
||||
if let tauri::WindowEvent::Focused(false) = event {
|
||||
let _ = win_handle.hide();
|
||||
let _ = app_handle.emit("quickpanel-hide", ());
|
||||
let _ = app_handle.emit(crate::constants::events::QUICKPANEL_HIDE, ());
|
||||
}
|
||||
});
|
||||
|
||||
eprintln!("[quickpanel] 弹窗窗口已预创建(隐藏状态)");
|
||||
crate::logger::log_info("quickpanel", "弹窗窗口已预创建(隐藏状态)");
|
||||
}
|
||||
|
||||
/// 应用启动时预创建弹窗窗口(隐藏)。
|
||||
@@ -250,41 +202,46 @@ pub fn show_popup(app: &AppHandle) {
|
||||
let (wa_left, wa_top, wa_right, wa_bottom) = get_work_area_at_point(mx, my)
|
||||
.unwrap_or((0, 0, 1920, 1040));
|
||||
|
||||
// 获取光标所在显示器的 DPI,将物理坐标转为逻辑坐标(DIP)
|
||||
// 光标所在显示器的 DPI:窗口尺寸需按物理像素换算
|
||||
let dpi = get_dpi_for_point(mx, my).unwrap_or(96);
|
||||
let scale = dpi as f64 / 96.0;
|
||||
let win_w_px = WIN_W * scale;
|
||||
let win_h_px = WIN_H * scale;
|
||||
|
||||
let wa_left_l = wa_left as f64 / scale;
|
||||
let wa_top_l = wa_top as f64 / scale;
|
||||
let wa_right_l = wa_right as f64 / scale;
|
||||
let wa_bottom_l = wa_bottom as f64 / scale;
|
||||
|
||||
// 直接以物理坐标计算(光标 + 工作区均为物理像素,避免混合 DPI 下换算偏移)
|
||||
let (x, y) = if cursor_mode {
|
||||
// 鼠标位置模式:以鼠标为基准偏移,clamp 到工作区内
|
||||
let mx_l = mx as f64 / scale;
|
||||
let my_l = my as f64 / scale;
|
||||
let x = (mx_l + 12.0).min(wa_right_l - WIN_W).max(wa_left_l);
|
||||
let y = (my_l + 12.0).min(wa_bottom_l - WIN_H).max(wa_top_l);
|
||||
let x = (mx as f64 + 12.0 * scale).min(wa_right as f64 - win_w_px).max(wa_left as f64);
|
||||
let y = (my as f64 + 12.0 * scale).min(wa_bottom as f64 - win_h_px).max(wa_top as f64);
|
||||
(x, y)
|
||||
} else {
|
||||
// 中央模式:窗口居中于鼠标所在显示器工作区
|
||||
let wa_w = wa_right_l - wa_left_l;
|
||||
let wa_h = wa_bottom_l - wa_top_l;
|
||||
(wa_left_l + (wa_w - WIN_W) / 2.0, wa_top_l + (wa_h - WIN_H) / 2.0)
|
||||
let wa_w = (wa_right - wa_left) as f64;
|
||||
let wa_h = (wa_bottom - wa_top) as f64;
|
||||
(
|
||||
wa_left as f64 + (wa_w - win_w_px) / 2.0,
|
||||
wa_top as f64 + (wa_h - win_h_px) / 2.0,
|
||||
)
|
||||
};
|
||||
|
||||
// 窗口已存在:移动 + 显示 + 请求焦点
|
||||
if let Some(win) = app.get_webview_window(POPUP_LABEL) {
|
||||
let _ = win.set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));
|
||||
let _ = win.set_position(tauri::Position::Physical(tauri::PhysicalPosition {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
}));
|
||||
let _ = win.show();
|
||||
let _ = win.set_focus();
|
||||
// 通知前端刷新数据
|
||||
let _ = app.emit("quickpanel-show", ());
|
||||
let _ = app.emit(crate::constants::events::QUICKPANEL_SHOW, ());
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:窗口被销毁时重新创建(隐藏),等前端 onMounted 回调 show_window
|
||||
POPUP_PENDING_SHOW.store(true, Ordering::SeqCst);
|
||||
if let Ok(mut pos) = PENDING_POS.lock() {
|
||||
*pos = Some((x, y));
|
||||
}
|
||||
create_popup_window(app);
|
||||
}
|
||||
|
||||
@@ -296,10 +253,18 @@ pub fn show_window(app: &AppHandle) {
|
||||
return;
|
||||
}
|
||||
if let Some(win) = app.get_webview_window(POPUP_LABEL) {
|
||||
// 应用 show_popup 计算的兜底位置(物理坐标),避免停留在屏幕外
|
||||
let pos = PENDING_POS.lock().ok().and_then(|p| *p);
|
||||
if let Some((x, y)) = pos {
|
||||
let _ = win.set_position(tauri::Position::Physical(tauri::PhysicalPosition {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
}));
|
||||
}
|
||||
let _ = win.show();
|
||||
let _ = win.set_focus();
|
||||
// 通知前端刷新数据
|
||||
let _ = app.emit("quickpanel-show", ());
|
||||
let _ = app.emit(crate::constants::events::QUICKPANEL_SHOW, ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user