1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use crate::Result;
use quicklook::QuickLookHandle;
use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Manager, Runtime};
use crate::{models::*, Error, PANEL};
pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>,
quicklook_handle: QuickLookHandle,
_api: PluginApi<R, C>,
) -> crate::Result<Quicklook<R>> {
Ok(Quicklook {
app_handle: app.clone(),
quicklook_handle,
})
}
/// Access to the quicklook APIs.
pub struct Quicklook<R: Runtime> {
app_handle: AppHandle<R>,
quicklook_handle: QuickLookHandle,
}
impl<R: Runtime> Quicklook<R> {
/// Sets the preview pane's items and reloads the pane.
///
/// **IMPORTANT**: If you change the URLs or order of the items you MUST
/// call [`Quicklook::queue_reload_if_dirty`] after for your changes to take visual effect.
/// However, if you are only updating the source frames of pre-existing items you
/// can safely avoid reloading.
pub fn set_items(&self, items: Vec<PreviewItem>) -> Result<()> {
self.set_items_raw(
items
.into_iter()
.map(|i| {
let frame = match i.src_frame {
Some(SourceFrame::Screen(rect)) => {
Some(quicklook::SourceFrame::Screen(rect))
}
Some(SourceFrame::Window { window_label, rect }) => self
.app_handle
.get_webview_window(&window_label)
.and_then(|w| {
quicklook::SourceFrame::window(
&w,
rect.x,
rect.y,
rect.width,
rect.height,
)
}),
None => None,
};
quicklook::PreviewItem::from_url_string(i.url, frame)
.ok_or(Error::NSURLMalformedURLString)
})
.collect::<Result<Vec<quicklook::PreviewItem>>>()?,
)?;
Ok(())
}
/// Sets the preview pane's items using [`quicklook::PreviewItem`] and reloads
/// the pane. This is useful if you want to manually initialize `NSURL`s yourself.
///
/// **IMPORTANT**: If you change the URLs or order of the items you MUST
/// call [`Quicklook::queue_reload_if_dirty`] after for your changes to take visual effect.
/// However, if you are only updating the source frames of pre-existing items you
/// can safely avoid reloading — including an item regaining a frame it previously
/// lacked, which this method detects and resolves with a panel reload on its own
/// (see [`quicklook::QuickLookPanel::upgrade_animation_for_restored_frames`]).
///
/// ## See Also
/// - [`quicklook::PreviewItem`]
/// - [`NSURL`](https://docs.rs/objc2-foundation/latest/objc2_foundation/struct.NSURL.html)
pub fn set_items_raw(&self, items: Vec<quicklook::PreviewItem>) -> Result<()> {
self.quicklook_handle.set_items(items);
// A frame coming back to an item that had none (e.g. a tracked element
// scrolling back into view) is the one frame-only update the open panel
// won't honor by itself: it commits to the fade animation when it opens
// without a frame and never re-asks. Upgrade it to the zoom on the main
// thread when that happens.
if self.quicklook_handle.take_frame_restored() {
self.app_handle
.run_on_main_thread(|| {
PANEL.with(|p| {
// SAFETY: OnceCell is initialized in `setup`
p.get().unwrap().upgrade_animation_for_restored_frames();
});
})
.map_err(|_| Error::MainThreadDispatchFailed)?;
}
Ok(())
}
/// Queues a panel reload that will be executed if the list of items has
/// been modified.
pub fn queue_reload_if_dirty(&self) -> Result<()> {
self.app_handle
.run_on_main_thread(|| {
PANEL.with(|p| {
// SAFETY: OnceCell is initialized in `setup`
p.get().unwrap().reload_if_dirty();
});
})
.map_err(|_| Error::MainThreadDispatchFailed)?;
Ok(())
}
/// Queues the preview panel to be shown.
pub fn queue_show(&self) -> Result<()> {
self.app_handle
.run_on_main_thread(|| {
PANEL.with(|p| {
p.get().unwrap().show();
})
})
.map_err(|_| Error::MainThreadDispatchFailed)?;
Ok(())
}
/// Queues the preview panel to be hidden.
pub fn queue_hide(&self) -> Result<()> {
self.app_handle
.run_on_main_thread(|| {
PANEL.with(|p| {
p.get().unwrap().hide();
})
})
.map_err(|_| Error::MainThreadDispatchFailed)?;
Ok(())
}
/// Queues the preview pane to be hidden if its currently
/// visible, and vice versa if its currently shown.
pub fn queue_toggle_visible(&self) -> Result<()> {
self.app_handle
.run_on_main_thread(|| {
PANEL.with(|p| {
p.get().unwrap().toggle_visible();
})
})
.map_err(|_| Error::MainThreadDispatchFailed)?;
Ok(())
}
}