1use tauri::{
2 plugin::{Builder, TauriPlugin},
3 Manager, Runtime,
4};
5
6mod desktop;
7mod error;
8#[cfg(windows)]
9mod snap;
10
11pub use desktop::{Frame, WebviewWindowExt};
12pub use error::{Error, Result};
13
14#[cfg(windows)]
15use std::sync::{
16 atomic::{AtomicBool, AtomicU32, Ordering},
17 OnceLock,
18};
19
20#[cfg(windows)]
21use tauri::Emitter;
22
23#[cfg(windows)]
24static TITLEBAR_HEIGHT: AtomicU32 = AtomicU32::new(32);
25#[cfg(windows)]
26static BUTTON_WIDTH: AtomicU32 = AtomicU32::new(46);
27#[cfg(windows)]
28static AUTO_TITLEBAR: AtomicBool = AtomicBool::new(false);
29#[cfg(windows)]
30static NATIVE_SNAP_OVERLAY: AtomicBool = AtomicBool::new(true);
31#[cfg(windows)]
32static CLOSE_HOVER_BG: OnceLock<String> = OnceLock::new();
33#[cfg(windows)]
34static BUTTON_HOVER_BG_LIGHT: OnceLock<String> = OnceLock::new();
35#[cfg(windows)]
36static BUTTON_HOVER_BG_DARK: OnceLock<String> = OnceLock::new();
37
38pub struct FramePluginBuilder {
39 #[cfg(windows)]
40 titlebar_height: u32,
41 #[cfg(windows)]
42 button_width: u32,
43 #[cfg(windows)]
44 auto_titlebar: bool,
45 #[cfg(windows)]
46 snap_overlay: bool,
47 #[cfg(windows)]
48 close_hover_bg: String,
49 #[cfg(windows)]
50 button_hover_bg_light: String,
51 #[cfg(windows)]
52 button_hover_bg_dark: String,
53}
54
55impl Default for FramePluginBuilder {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl FramePluginBuilder {
62 pub fn new() -> Self {
63 Self {
64 #[cfg(windows)]
65 titlebar_height: 32,
66 #[cfg(windows)]
67 button_width: 46,
68 #[cfg(windows)]
69 auto_titlebar: false,
70 #[cfg(windows)]
71 snap_overlay: true,
72 #[cfg(windows)]
73 close_hover_bg: "rgba(196,43,28,1)".into(),
74 #[cfg(windows)]
75 button_hover_bg_light: "rgba(0,0,0,0.1)".into(),
76 #[cfg(windows)]
77 button_hover_bg_dark: "rgba(255,255,255,0.1)".into(),
78 }
79 }
80
81 #[cfg(windows)]
82 pub fn titlebar_height(mut self, height: u32) -> Self {
83 self.titlebar_height = height;
84 self
85 }
86
87 #[cfg(not(windows))]
88 pub fn titlebar_height(self, _: u32) -> Self {
89 self
90 }
91
92 #[cfg(windows)]
93 pub fn button_width(mut self, width: u32) -> Self {
94 self.button_width = width;
95 self
96 }
97
98 #[cfg(not(windows))]
99 pub fn button_width(self, _: u32) -> Self {
100 self
101 }
102
103 #[cfg(windows)]
104 pub fn auto_titlebar(mut self, auto: bool) -> Self {
105 self.auto_titlebar = auto;
106 self
107 }
108
109 #[cfg(not(windows))]
110 pub fn auto_titlebar(self, _: bool) -> Self {
111 self
112 }
113
114 #[cfg(windows)]
115 pub fn snap_overlay(mut self, enabled: bool) -> Self {
116 self.snap_overlay = enabled;
117 self
118 }
119
120 #[cfg(not(windows))]
121 pub fn snap_overlay(self, _: bool) -> Self {
122 self
123 }
124
125 #[cfg(windows)]
126 pub fn close_hover_bg(mut self, color: impl Into<String>) -> Self {
127 self.close_hover_bg = color.into();
128 self
129 }
130
131 #[cfg(not(windows))]
132 pub fn close_hover_bg(self, _: impl Into<String>) -> Self {
133 self
134 }
135
136 #[cfg(windows)]
138 pub fn button_hover_bg_light(mut self, color: impl Into<String>) -> Self {
139 self.button_hover_bg_light = color.into();
140 self
141 }
142
143 #[cfg(not(windows))]
144 pub fn button_hover_bg_light(self, _: impl Into<String>) -> Self {
145 self
146 }
147
148 #[cfg(windows)]
150 pub fn button_hover_bg_dark(mut self, color: impl Into<String>) -> Self {
151 self.button_hover_bg_dark = color.into();
152 self
153 }
154
155 #[cfg(not(windows))]
156 pub fn button_hover_bg_dark(self, _: impl Into<String>) -> Self {
157 self
158 }
159
160 #[cfg(windows)]
162 pub fn button_hover_bg(mut self, color: impl Into<String>) -> Self {
163 let c = color.into();
164 self.button_hover_bg_light = c.clone();
165 self.button_hover_bg_dark = c;
166 self
167 }
168
169 #[cfg(not(windows))]
170 pub fn button_hover_bg(self, _: impl Into<String>) -> Self {
171 self
172 }
173
174 #[cfg(windows)]
175 pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
176 TITLEBAR_HEIGHT.store(self.titlebar_height, Ordering::SeqCst);
177 BUTTON_WIDTH.store(self.button_width, Ordering::SeqCst);
178 AUTO_TITLEBAR.store(self.auto_titlebar, Ordering::SeqCst);
179 NATIVE_SNAP_OVERLAY.store(self.snap_overlay, Ordering::SeqCst);
180 let _ = CLOSE_HOVER_BG.set(self.close_hover_bg);
181 let _ = BUTTON_HOVER_BG_LIGHT.set(self.button_hover_bg_light);
182 let _ = BUTTON_HOVER_BG_DARK.set(self.button_hover_bg_dark);
183
184 Builder::new("frame")
185 .setup(|app, _| {
186 app.manage(Frame::new(app.clone()));
187 Ok(())
188 })
189 .on_page_load(|webview, _| {
190 let _ = webview.emit("frame-page-load", ());
191 if !AUTO_TITLEBAR.load(Ordering::SeqCst) {
192 return;
193 }
194 let height = TITLEBAR_HEIGHT.load(Ordering::SeqCst);
195 let button_width = BUTTON_WIDTH.load(Ordering::SeqCst);
196 let snap_overlay = NATIVE_SNAP_OVERLAY.load(Ordering::SeqCst);
197 let webview = webview.clone();
198 tauri::async_runtime::spawn(async move {
199 let _ = webview.eval(build_scripts(height, None));
200 if snap_overlay {
201 let _ = crate::snap::install_window(&webview.window(), height, button_width, 1);
203 }
204 });
205 })
206 .build()
207 }
208
209 #[cfg(not(windows))]
210 pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
211 Builder::new("frame")
212 .setup(|app, _| {
213 app.manage(Frame::new(app.clone()));
214 Ok(())
215 })
216 .build()
217 }
218}
219
220pub fn init<R: Runtime>() -> TauriPlugin<R> {
221 FramePluginBuilder::new().build()
222}
223
224#[cfg(windows)]
225pub(crate) fn snap_overlay_enabled() -> bool {
226 NATIVE_SNAP_OVERLAY.load(Ordering::SeqCst)
227}
228
229
230#[cfg(windows)]
231pub(crate) fn get_titlebar_height() -> u32 {
232 TITLEBAR_HEIGHT.load(Ordering::SeqCst)
233}
234
235#[cfg(windows)]
236pub(crate) fn get_button_width() -> u32 {
237 BUTTON_WIDTH.load(Ordering::SeqCst)
238}
239
240#[cfg(windows)]
241pub(crate) fn get_auto_titlebar() -> bool {
242 AUTO_TITLEBAR.load(Ordering::SeqCst)
243}
244
245#[cfg(windows)]
246pub(crate) fn build_scripts(height: u32, controls: Option<Vec<&str>>) -> String {
247 let height_px = format!("\"{}px\"", height);
248 let width_px = format!("\"{}px\"", BUTTON_WIDTH.load(Ordering::SeqCst));
249 let close_hover = CLOSE_HOVER_BG
250 .get()
251 .map_or("rgba(196,43,28,1)", |s| s.as_str());
252 let button_hover_light = BUTTON_HOVER_BG_LIGHT
253 .get()
254 .map_or("rgba(0,0,0,0.1)", |s| s.as_str());
255 let button_hover_dark = BUTTON_HOVER_BG_DARK
256 .get()
257 .map_or("rgba(255,255,255,0.1)", |s| s.as_str());
258
259 let script_tb = include_str!("js/titlebar.js").replace("\"32px\"", &height_px);
260 let mut script_controls = include_str!("js/controls.js")
261 .replace("\"32px\"", &height_px)
262 .replace("\"46px\"", &width_px)
263 .replace("\"__CLOSE_HOVER_BG__\"", &format!("\"{}\"", close_hover))
264 .replace("\"__BUTTON_HOVER_BG_LIGHT__\"", &format!("\"{}\"", button_hover_light))
265 .replace("\"__BUTTON_HOVER_BG_DARK__\"", &format!("\"{}\"", button_hover_dark));
266
267 if let Some(ctrl) = controls {
268 script_controls = script_controls.replacen(
269 "[\"minimize\", \"maximize\", \"close\"]",
270 &format!("{:?}", ctrl),
271 1,
272 );
273 }
274
275 format!("{}\n{}", script_tb, script_controls)
276}
277
278pub trait FrameExt<R: Runtime> {
279 fn frame(&self) -> &Frame<R>;
280}
281
282impl<R: Runtime, T: Manager<R>> FrameExt<R> for T {
283 fn frame(&self) -> &Frame<R> {
284 self.state::<Frame<R>>().inner()
285 }
286}