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