Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions QuickLookOptimize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Quick Look 优化规划

## 1. 现状总结

- **功能层面**: Cardinal 当前通过 Tauri 指令将文件路径推送给 `QLPreviewPanel`,实现了基础的预览功能。
- **控制层面**: `PreviewController` 作为数据源和代理,通过主线程的 `thread_local!` 管理,但缺少丰富的生命周期和事件管理。
- **体验层面**:
- 前端仅在按下空格时**打开**面板,缺少“切换”语义,也无法感知面板的关闭状态。
- 面板动画、键盘控制、当前预览项与UI列表的同步缺失,与系统 Finder 的原生体验差距明显。

## 2. 核心目标

1. **原生体验对齐**: 实现平滑的缩放动画、键盘导航等,让 Quick Look 的交互体感与系统原生行为一致。
2. **双向状态同步**: 建立前端 UI 与原生 Quick Look 面板之间的状态同步机制,确保两者状态始终一致。
3. **提升交互健壮性**: 优化指令设计,处理各种边界情况,提供更可靠、更符合直觉的用户体验。

## 3. 优化实施计划

### Phase 2: 原生视觉与动画打磨

此阶段专注于提升视觉效果,达到“原生感”。

**2.1. 实现平滑的缩放动画**
- **问题**: 面板生硬地出现,没有从列表项“放大”的动画效果。
- **方案**:
- **前端**: 触发 `toggle_quicklook` 时,使用 `element.getBoundingClientRect()` 获取选中项的 DOM 坐标,并将其作为参数传递给 Rust。
- **Rust**:
- 在 `PreviewController` 中存储每个文件路径及其对应的屏幕坐标 (`tauri::Rect`)。
- 实现 `QLPreviewPanelDelegate` 的 `previewPanel:sourceFrameOnScreenForPreviewItem:` 方法。
- 在该方法中,根据 `item` 的 `previewItemURL` 查找到其坐标,结合窗口位置计算出**屏幕绝对坐标**并返回。

**2.2. (可选) 提供过渡图像**
- **问题**: 动画过程中可能出现短暂的白色闪烁。
- **方案**:
- 如果前端有文件图标或缩略图的缓存(例如 Base64 或 URL),可以将其一并传递给 Rust。
- 在 Rust 中实现 `previewPanel:transitionImageForPreviewItem:contentRect:` 方法,返回对应的 `NSImage`,以提供更平滑的过渡效果。

### Phase 3: 高级交互

此阶段实现更精细的交互控制。

**3.1. 实现键盘事件透传**
- **问题**: Quick Look 激活时,它会成为 Key Window,导致文件列表无法响应方向键等导航快捷键。
- **方案**:
- 在 `PreviewController` 中实现 `QLPreviewPanelDelegate` 的 `handleEvent:` 方法。
- 捕获关键的键盘事件(如上/下箭头、删除键等)。
- 判断事件类型后,通过 `app_handle.emit_all("quicklook-keydown", ...)` 将事件信息转发给前端。
- 前端监听此事件,并执行对应的列表导航或操作逻辑,从而实现 Quick Look 面板对文件列表的“遥控”。

## 4. 预期收益

- **交互一致性**: 提供与系统 Finder 高度一致的交互与动画体验。
- **状态可靠性**: 通过双向事件同步,确保面板与前端状态一致,减少用户困惑和无效的后台调用。
- **架构融合度**: 通过键盘事件透传和几何信息传递,将原生 Quick Look 面板更无缝地融入到现有的虚拟列表架构中。
41 changes: 41 additions & 0 deletions cardinal/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cardinal/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
base64 = "0.22"
rayon = "1.10"
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSString", "NSURL"] }
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSWindow", "NSPanel", "NSResponder"] }
objc2-quick-look-ui = "0.3"
parking_lot = "0.12"
tauri-plugin-prevent-default = "4"
directories = "6.0.0"
Expand All @@ -42,6 +46,7 @@ search-cache.path = "../../search-cache"
fswalk.path = "../../fswalk"
fs-icon.path = "../../fs-icon"
search-cancel.path = "../../search-cancel"
camino = "1.2.1"

[features]
default = []
Expand Down
52 changes: 42 additions & 10 deletions cardinal/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{
LOGIC_START,
lifecycle::{EXIT_REQUESTED, load_app_state},
quicklook::{
QuickLookItemInput, close_preview_panel, toggle_preview_panel, update_preview_panel,
},
window_controls::{WindowToggle, activate_window, hide_window, toggle_window},
};
use anyhow::Result;
Expand Down Expand Up @@ -97,6 +100,45 @@ impl NodeInfoMetadata {
}
}

#[tauri::command]
pub fn close_quicklook(app_handle: AppHandle) -> Result<(), String> {
let app_handle_cloned = app_handle.clone();
app_handle
.run_on_main_thread(move || {
close_preview_panel(app_handle_cloned);
})
.map_err(|e| format!("Failed to dispatch quicklook action: {e:?}"))?;
Ok(())
}

#[tauri::command]
pub fn update_quicklook(
app_handle: AppHandle,
items: Vec<QuickLookItemInput>,
) -> Result<(), String> {
let app_handle_cloned = app_handle.clone();
app_handle
.run_on_main_thread(move || {
update_preview_panel(app_handle_cloned, items);
})
.map_err(|e| format!("Failed to dispatch quicklook action: {e:?}"))?;
Ok(())
}

#[tauri::command]
pub fn toggle_quicklook(
app_handle: AppHandle,
items: Vec<QuickLookItemInput>,
) -> Result<(), String> {
let app_handle_cloned = app_handle.clone();
app_handle
.run_on_main_thread(move || {
toggle_preview_panel(app_handle_cloned, items);
})
.map_err(|e| format!("Failed to dispatch quicklook action: {e:?}"))?;
Ok(())
}

#[tauri::command]
pub async fn search(
query: String,
Expand Down Expand Up @@ -222,16 +264,6 @@ pub fn open_path(path: String) -> Result<(), String> {
Ok(())
}

#[tauri::command]
pub fn preview_with_quicklook(path: String) -> Result<(), String> {
Command::new("qlmanage")
.arg("-p")
.arg(&path)
.spawn()
.map_err(|e| format!("Failed to launch Quick Look preview: {e}"))?;
Ok(())
}

#[tauri::command]
pub fn request_app_exit(app_handle: AppHandle) -> Result<(), String> {
EXIT_REQUESTED.store(true, Ordering::Relaxed);
Expand Down
11 changes: 7 additions & 4 deletions cardinal/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod background;
mod commands;
mod lifecycle;
mod quicklook;
mod window_controls;

use anyhow::{Context, Result};
Expand All @@ -9,9 +10,9 @@ use background::{
};
use cardinal_sdk::EventWatcher;
use commands::{
SearchJob, SearchState, activate_main_window, get_app_status, get_nodes_info, hide_main_window,
open_in_finder, open_path, preview_with_quicklook, request_app_exit, search, start_logic,
toggle_main_window, trigger_rescan, update_icon_viewport,
SearchJob, SearchState, activate_main_window, close_quicklook, get_app_status, get_nodes_info,
hide_main_window, open_in_finder, open_path, request_app_exit, search, start_logic,
toggle_main_window, toggle_quicklook, trigger_rescan, update_icon_viewport, update_quicklook,
};
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender, bounded, unbounded};
use lifecycle::{
Expand Down Expand Up @@ -118,7 +119,9 @@ pub fn run() -> Result<()> {
trigger_rescan,
open_in_finder,
open_path,
preview_with_quicklook,
toggle_quicklook,
close_quicklook,
update_quicklook,
request_app_exit,
start_logic,
hide_main_window,
Expand Down
Loading