截图模块初始化

This commit is contained in:
zhongluofeng
2026-07-31 18:30:55 +08:00
parent 9d8f963cc6
commit 66575c6166
16 changed files with 1011 additions and 481 deletions
+92 -58
View File
@@ -20,7 +20,11 @@ use tauri::{
/// 记录窗口最后显示时间,用于失焦防抖(避免显示瞬间因焦点未稳定而被立即隐藏)
static LAST_SHOW_TIME: Mutex<Option<Instant>> = Mutex::new(None);
use crate::clipboard::popup::{get_cursor_pos, get_work_area};
/// 保存最近一次右键时计算出的定位参数(逻辑坐标),供 `tray_menu_ready` 使用
/// (x, tray_top_l, wa_top_l, wa_bottom_l, scale)
static LAST_MENU_LAYOUT: Mutex<Option<(f64, f64, f64, f64, f64)>> = Mutex::new(None);
use crate::clipboard::popup::{get_work_area, get_work_area_at_point, get_dpi_for_point};
use crate::clipboard::ClipboardManager;
use crate::download_engine::DownloadEngine;
use crate::mihomo_manager::MihomoManager;
@@ -211,7 +215,8 @@ pub async fn get_tray_menu_state(app: &AppHandle) -> TrayMenuState {
// ===== 显示/隐藏托盘菜单窗口 =====
/// 托盘菜单窗口尺寸(逻辑像素)
const MENU_W: f64 = 260.0;
/// 宽度需与前端 TrayMenu.vue 的 MENU_WIDTH 保持一致,避免 resize 时右对齐错位
const MENU_W: f64 = 220.0;
const MENU_H: f64 = 420.0;
/// 预创建隐藏的托盘菜单窗口(在应用启动时调用)。
@@ -272,26 +277,37 @@ pub fn precreate_tray_menu_window(app: &AppHandle) {
eprintln!("[tray-menu] 菜单窗口已预创建(隐藏渲染)");
}
/// 在鼠标当前位置显示托盘菜单窗口。
/// 窗口不存在则创建并显示,已存在则移动到鼠标位置并显示
pub fn show_tray_menu(app: &AppHandle) {
let (mx, my) = match get_cursor_pos() {
Some(p) => p,
None => return,
};
/// 右键托盘时调用:计算定位参数、发送状态给前端,但不立即显示窗口。
/// 窗口等待前端测量内容高度后调用 `tray_menu_ready` 才显示,确保底部精确对齐托盘图标
///
/// `cursor_pos`: 事件报告的鼠标物理坐标;`tray_rect`: 托盘图标区域(物理像素)。
pub fn show_tray_menu(app: &AppHandle, cursor_pos: (f64, f64), tray_rect: (f64, f64, f64, f64)) {
let (mx, my) = (cursor_pos.0, cursor_pos.1);
let tray_top = tray_rect.1;
// GetCursorPos 在 Per-Monitor DPI V2 下返回物理像素,直接用作 PhysicalPosition
let win = app.get_webview_window(TRAY_MENU_LABEL);
let scale = win.as_ref()
.and_then(|w| w.scale_factor().ok())
.unwrap_or(1.0);
// 获取光标所在显示器的工作区(物理像素)
let (wa_left, wa_top, wa_right, wa_bottom) = get_work_area_at_point(mx as i32, my as i32)
.unwrap_or((0, 0, 1920, 1040));
let w_phys = (MENU_W * scale) as i32;
let h_phys = (MENU_H * scale) as i32;
// 获取光标所在显示器的 DPI,将物理坐标转为逻辑坐标(DIP)
let dpi = get_dpi_for_point(mx as i32, my as i32).unwrap_or(96);
let scale = dpi as f64 / 96.0;
// 托盘位于屏幕右下角,菜单始终出现在鼠标左上方
let x = (mx - w_phys).max(0);
let y = (my - h_phys - 8).max(0);
let mx_l = mx / scale;
let tray_top_l = tray_top / scale;
let wa_left_l = wa_left as f64 / scale;
let wa_right_l = wa_right as f64 / scale;
let wa_top_l = wa_top as f64 / scale;
let wa_bottom_l = wa_bottom as f64 / scale;
// 水平:菜单左边缘对齐鼠标 X(向右延伸),超出右边界则左移
let x = mx_l.max(wa_left_l).min(wa_right_l - MENU_W);
// 保存布局参数,供 tray_menu_ready 使用
{
let mut layout = LAST_MENU_LAYOUT.lock().unwrap();
*layout = Some((x, tray_top_l, wa_top_l, wa_bottom_l, scale));
}
// 记录显示时间,用于失焦防抖
{
@@ -299,42 +315,17 @@ pub fn show_tray_menu(app: &AppHandle) {
*t = Some(Instant::now());
}
// 窗口存在(预创建或之前显示过):移动 + 显示 + 发送状态
if let Some(win) = win {
let _ = win.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
let _ = win.show();
let _ = win.set_focus();
let app_clone = app.clone();
tauri::async_runtime::spawn(async move {
let state = get_tray_menu_state(&app_clone).await;
let _ = app_clone.emit("tray-menu-show", state);
});
return;
// 确保窗口存在(兖底)
if app.get_webview_window(TRAY_MENU_LABEL).is_none() {
precreate_tray_menu_window(app);
}
// 兜底:窗口未预创建(理论上不会走到,因为 create_tray_menu 已预创建
precreate_tray_menu_window(app);
if let Some(win) = app.get_webview_window(TRAY_MENU_LABEL) {
let _ = win.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
let _ = win.show();
let _ = win.set_focus();
let app_clone = app.clone();
tauri::async_runtime::spawn(async move {
let state = get_tray_menu_state(&app_clone).await;
let _ = app_clone.emit("tray-menu-show", state);
});
}
}
/// 预创建窗口完成后由前端调用(仅触发状态推送,不显示窗口——预创建模式下窗口保持隐藏)。
pub fn show_window(app: &AppHandle) {
if app.get_webview_window(TRAY_MENU_LABEL).is_some() {
let app_clone = app.clone();
tauri::async_runtime::spawn(async move {
let state = get_tray_menu_state(&app_clone).await;
let _ = app_clone.emit("tray-menu-show", state);
});
}
// 发送状态给前端(前端测量内容高度后调用 tray_menu_ready 显示窗口
let app_clone = app.clone();
tauri::async_runtime::spawn(async move {
let state = get_tray_menu_state(&app_clone).await;
let _ = app_clone.emit("tray-menu-show", state);
});
}
/// 隐藏托盘菜单窗口(不销毁,保留复用)
@@ -403,6 +394,11 @@ pub async fn tray_menu_action(
let _ = app.emit("tray:new-download", ());
hide_tray_menu(&app);
}
"screenshot_region" => {
hide_tray_menu(&app);
// 主窗口(隐藏运行中)接收事件后调用 screenshotStore.startCapture('region')
let _ = app.emit("tray:start-screenshot", "region");
}
"settings" => {
if let Some(window) = app.get_webview_window("main") {
window.show().ok();
@@ -433,10 +429,35 @@ pub async fn tray_menu_hide(app: AppHandle) -> Result<(), String> {
Ok(())
}
/// 显示已创建的菜单窗口(前端 onMounted 后调用)
/// 前端测量内容高度后调用:调整窗口大小、精确定位、然后显示。
/// 这样窗口从一开始就是正确尺寸和位置,不会出现间隙。
#[tauri::command]
pub async fn tray_menu_show_window(app: AppHandle) -> Result<(), String> {
show_window(&app);
pub async fn tray_menu_ready(content_height: f64, app: AppHandle) -> Result<(), String> {
let win = app.get_webview_window(TRAY_MENU_LABEL)
.ok_or("tray-menu window not found")?;
let (x, tray_top_l, wa_top_l, wa_bottom_l, _scale) = {
let layout = LAST_MENU_LAYOUT.lock().unwrap();
layout.unwrap_or((0.0, 1040.0, 0.0, 1040.0, 1.0))
};
// 将内容高度限制在合理范围内
let h = content_height.max(100.0).min(520.0);
// 调整窗口尺寸
let _ = win.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: MENU_W,
height: h,
}));
// 垂直:菜单下边缘紧贴托盘图标顶部(向上弹出)
let y = (tray_top_l - h).max(wa_top_l).min(wa_bottom_l - h);
let pos = tauri::Position::Logical(tauri::LogicalPosition { x, y });
let _ = win.set_position(pos);
let _ = win.show();
let _ = win.set_focus();
Ok(())
}
@@ -695,10 +716,23 @@ pub fn create_tray_menu(app: &AppHandle) -> Result<(), tauri::Error> {
}
TrayIconEvent::Click {
button: MouseButton::Right,
position,
rect,
..
} => {
// 右键:显示自定义菜单窗口
show_tray_menu(&app);
// 右键:显示自定义菜单窗口(使用事件中的精确坐标)
let cursor = (position.x, position.y);
// 从 Rect 的 Position/Size 枚举中提取物理像素值
let (rx, ry) = match rect.position {
tauri::Position::Physical(p) => (p.x as f64, p.y as f64),
tauri::Position::Logical(p) => (p.x, p.y),
};
let (_rw, rh) = match rect.size {
tauri::Size::Physical(s) => (s.width as f64, s.height as f64),
tauri::Size::Logical(s) => (s.width, s.height),
};
let tray_r = (rx, ry, _rw, rh);
show_tray_menu(&app, cursor, tray_r);
}
_ => {}
}