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