Skip to main content

tauri_plugin_decor/
decor.rs

1use serde::Deserialize;
2use tauri::{Manager, Runtime, WebviewWindow};
3
4use crate::error;
5use crate::overlay;
6
7#[derive(Clone, Default, Debug, Deserialize)]
8#[serde(default, rename_all = "snake_case")]
9pub struct DecorStyle {
10    pub controls_height: Option<f64>,
11    pub controls_inset_x: Option<f64>,
12    pub controls_spacing: Option<f64>,
13    pub controls_scale: Option<f64>,
14    pub controls_button_width: Option<u32>,
15    pub controls_close_hover_bg: Option<String>,
16    pub controls_button_hover_bg: Option<String>,
17}
18
19pub struct Decor<R: Runtime> {
20    app: tauri::AppHandle<R>,
21}
22
23impl<R: Runtime> Decor<R> {
24    pub fn new(app: tauri::AppHandle<R>) -> Self {
25        Self { app }
26    }
27
28    pub fn reconfigure(&self, style: DecorStyle) {
29        if let Some(v) = style.controls_scale {
30            self.set_controls_scale(v);
31        }
32        if let Some(v) = style.controls_height {
33            self.set_controls_height(v);
34        }
35        if let Some(v) = style.controls_inset_x {
36            self.set_controls_inset_x(v);
37        }
38        if let Some(v) = style.controls_spacing {
39            self.set_controls_spacing(v);
40        }
41        if let Some(v) = style.controls_button_width {
42            self.set_controls_button_width(v);
43        }
44        if let Some(v) = style.controls_close_hover_bg {
45            self.set_controls_close_hover_bg(v);
46        }
47        if let Some(v) = style.controls_button_hover_bg {
48            self.set_controls_button_hover_bg(v);
49        }
50    }
51
52    pub fn set_controls_height(&self, height: f64) {
53        #[cfg(any(target_os = "windows", target_os = "linux"))]
54        {
55            crate::config::set_titlebar_height(height.round().max(0.0) as u32);
56            self.refresh_html();
57        }
58        #[cfg(target_os = "macos")]
59        {
60            crate::config::set_traffic_inset_y(height);
61            crate::traffic::reposition_all(&self.app);
62        }
63        #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
64        let _ = height;
65    }
66
67    pub fn set_controls_inset_x(&self, inset: f64) {
68        #[cfg(target_os = "macos")]
69        {
70            crate::config::set_traffic_inset_x(inset);
71            crate::traffic::reposition_all(&self.app);
72        }
73        #[cfg(not(target_os = "macos"))]
74        let _ = inset;
75    }
76
77    pub fn set_controls_spacing(&self, spacing: f64) {
78        #[cfg(target_os = "macos")]
79        {
80            crate::config::set_traffic_spacing(spacing);
81            crate::traffic::reposition_all(&self.app);
82        }
83        #[cfg(not(target_os = "macos"))]
84        let _ = spacing;
85    }
86
87    pub fn set_controls_scale(&self, scale: f64) {
88        #[cfg(target_os = "macos")]
89        {
90            crate::config::set_traffic_scale(scale);
91            crate::traffic::reposition_all(&self.app);
92        }
93        #[cfg(any(target_os = "windows", target_os = "linux"))]
94        {
95            crate::config::set_controls_scale(scale);
96            self.refresh_html();
97        }
98        #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
99        let _ = scale;
100    }
101
102    pub fn set_controls_button_width(&self, width: u32) {
103        #[cfg(any(target_os = "windows", target_os = "linux"))]
104        {
105            crate::config::set_button_width(width);
106            self.refresh_html();
107        }
108        #[cfg(not(any(target_os = "windows", target_os = "linux")))]
109        let _ = width;
110    }
111
112    pub fn set_controls_close_hover_bg(&self, color: impl Into<String>) {
113        #[cfg(any(target_os = "windows", target_os = "linux"))]
114        {
115            crate::config::set_close_hover_bg(color);
116            self.refresh_html();
117        }
118        #[cfg(not(any(target_os = "windows", target_os = "linux")))]
119        let _ = color.into();
120    }
121
122    pub fn set_controls_button_hover_bg(&self, color: impl Into<String>) {
123        #[cfg(any(target_os = "windows", target_os = "linux"))]
124        {
125            crate::config::set_button_hover_bg(color);
126            self.refresh_html();
127        }
128        #[cfg(not(any(target_os = "windows", target_os = "linux")))]
129        let _ = color.into();
130    }
131
132    #[cfg(target_os = "windows")]
133    fn refresh_html(&self) {
134        crate::windows::apply_runtime_style(&self.app);
135    }
136
137    #[cfg(target_os = "linux")]
138    fn refresh_html(&self) {
139        crate::linux::apply_runtime_style(&self.app);
140    }
141}
142
143pub trait DecorExt<R: Runtime> {
144    fn decor(&self) -> &Decor<R>;
145}
146
147impl<R: Runtime, T: Manager<R>> DecorExt<R> for T {
148    fn decor(&self) -> &Decor<R> {
149        self.state::<Decor<R>>().inner()
150    }
151}
152
153pub trait WebviewWindowExt {
154    fn create_overlay_titlebar(&self) -> error::Result<&WebviewWindow>;
155
156    #[cfg(target_os = "macos")]
157    fn make_transparent(&self) -> error::Result<&WebviewWindow>;
158
159    #[cfg(target_os = "macos")]
160    fn set_window_level(&self, level: u32) -> error::Result<&WebviewWindow>;
161}
162
163impl WebviewWindowExt for WebviewWindow {
164    fn create_overlay_titlebar(&self) -> error::Result<&WebviewWindow> {
165        #[cfg(target_os = "windows")]
166        self.set_decorations(false)?;
167
168        overlay::register(self.label());
169
170        #[cfg(target_os = "windows")]
171        {
172            let _ = self.eval(&crate::windows::build_scripts(
173                crate::config::titlebar_height(),
174            ));
175        }
176
177        Ok(self)
178    }
179
180    #[cfg(target_os = "macos")]
181    fn make_transparent(&self) -> error::Result<&WebviewWindow> {
182        use cocoa::{
183            appkit::NSColor,
184            base::{id, nil},
185            foundation::NSString,
186        };
187
188        self.with_webview(|webview| unsafe {
189            let id = webview.inner() as *mut objc::runtime::Object;
190            let no: id = msg_send![class!(NSNumber), numberWithBool: 0];
191            let _: id = msg_send![
192                id,
193                setValue: no
194                forKey: NSString::alloc(nil).init_str("drawsBackground")
195            ];
196        })?;
197
198        ensure_main_thread(self, move |win| {
199            let ns_win = win.ns_window()? as id;
200            unsafe {
201                let bg = NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0.0, 0.0, 0.0, 0.0);
202                let _: id = msg_send![ns_win, setBackgroundColor: bg];
203            }
204            Ok(win)
205        })
206    }
207
208    #[cfg(target_os = "macos")]
209    fn set_window_level(&self, level: u32) -> error::Result<&WebviewWindow> {
210        ensure_main_thread(self, move |win| {
211            let ns_win = win.ns_window()? as cocoa::base::id;
212            unsafe {
213                let _: () = msg_send![ns_win, setLevel: level];
214            }
215            Ok(win)
216        })
217    }
218}
219
220#[cfg(target_os = "macos")]
221fn is_main_thread() -> bool {
222    std::thread::current().name() == Some("main")
223}
224
225#[cfg(target_os = "macos")]
226fn ensure_main_thread<F>(win: &WebviewWindow, action: F) -> error::Result<&WebviewWindow>
227where
228    F: FnOnce(&WebviewWindow) -> error::Result<&WebviewWindow> + Send + 'static,
229{
230    if is_main_thread() {
231        action(win)?;
232        Ok(win)
233    } else {
234        let win2 = win.clone();
235        win.run_on_main_thread(move || {
236            let _ = action(&win2);
237        })?;
238        Ok(win)
239    }
240}