Skip to main content

tauri_plugin_frame/
lib.rs

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: 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}