tauri_plugin_frame/
lib.rs1use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
2use tauri::plugin::{Builder, TauriPlugin};
3use tauri::{async_runtime, Emitter, Error, Listener, Runtime, WebviewWindow};
4
5mod commands;
6
7static TITLEBAR_HEIGHT: AtomicU32 = AtomicU32::new(32);
8static AUTO_TITLEBAR: AtomicBool = AtomicBool::new(false);
9
10pub struct FramePluginBuilder {
12 titlebar_height: u32,
13 auto_titlebar: bool,
14}
15
16impl FramePluginBuilder {
17 pub fn new() -> Self {
18 Self {
19 titlebar_height: 32,
20 auto_titlebar: false,
21 }
22 }
23
24 pub fn titlebar_height(mut self, height: u32) -> Self {
26 self.titlebar_height = height;
27 self
28 }
29
30 pub fn auto_titlebar(mut self, auto: bool) -> Self {
32 self.auto_titlebar = auto;
33 self
34 }
35
36 pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
38 TITLEBAR_HEIGHT.store(self.titlebar_height, Ordering::SeqCst);
39 AUTO_TITLEBAR.store(self.auto_titlebar, Ordering::SeqCst);
40
41 Builder::new("frame")
42 .invoke_handler(tauri::generate_handler![commands::show_snap_overlay])
43 .on_page_load(|webview, _| {
44 let _ = webview.emit("frame-page-load", ());
45
46 if AUTO_TITLEBAR.load(Ordering::SeqCst) {
47 let height = TITLEBAR_HEIGHT.load(Ordering::SeqCst);
48 let webview = webview.clone();
49
50 async_runtime::spawn(async move {
51 let _ = webview.eval(build_scripts(height, None));
52 });
53 }
54 })
55 .build()
56 }
57}
58
59impl Default for FramePluginBuilder {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65pub fn init<R: Runtime>() -> TauriPlugin<R> {
67 FramePluginBuilder::new().build()
68}
69
70fn build_scripts(height: u32, controls: Option<Vec<&str>>) -> String {
71 let height_px = format!("\"{}px\"", height);
72
73 let script_tb = include_str!("js/titlebar.js").replace("\"32px\"", &height_px);
74 let mut script_controls = include_str!("js/controls.js").replace("\"32px\"", &height_px);
75
76 if let Some(ctrl) = controls {
77 script_controls = script_controls.replacen(
78 "[\"minimize\", \"maximize\", \"close\"]",
79 &format!("{:?}", ctrl),
80 1,
81 );
82 }
83
84 format!("{}\n{}", script_tb, script_controls)
85}
86
87pub trait WebviewWindowExt {
89 fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error>;
90 fn create_overlay_titlebar_with_height(&self, height: u32) -> Result<&WebviewWindow, Error>;
91}
92
93impl WebviewWindowExt for WebviewWindow {
94 fn create_overlay_titlebar(&self) -> Result<&WebviewWindow, Error> {
95 self.create_overlay_titlebar_with_height(TITLEBAR_HEIGHT.load(Ordering::SeqCst))
96 }
97
98 fn create_overlay_titlebar_with_height(&self, height: u32) -> Result<&WebviewWindow, Error> {
99 self.set_decorations(false)?;
100
101 let win = self.clone();
102 self.listen("frame-page-load", move |event| {
103 let controls: Vec<&str> = [
104 win.is_minimizable().unwrap_or(false).then_some("minimize"),
105 (win.is_maximizable().unwrap_or(false) && win.is_resizable().unwrap_or(false))
106 .then_some("maximize"),
107 win.is_closable().unwrap_or(false).then_some("close"),
108 ]
109 .into_iter()
110 .flatten()
111 .collect();
112
113 let _ = win.eval(build_scripts(height, Some(controls)));
114
115 let win2 = win.clone();
116 win.on_window_event(move |e| {
117 if matches!(e, tauri::WindowEvent::CloseRequested { .. }) {
118 win2.unlisten(event.id());
119 }
120 });
121 });
122
123 Ok(self)
124 }
125}