tauri_plugin_decor/
decor.rs1use 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}