1#[cfg(feature = "tray-icon")]
7use crate::Tray;
8use serde_repr::Deserialize_repr;
9#[cfg(feature = "tray-icon")]
10use tauri::Manager;
11#[cfg(feature = "tray-icon")]
12use tauri::Monitor;
13use tauri::{PhysicalPosition, PhysicalSize, Result, Runtime, WebviewWindow, Window};
14
15#[derive(Debug, Deserialize_repr)]
17#[repr(u16)]
18pub enum Position {
19 TopLeft = 0,
20 TopRight,
21 BottomLeft,
22 BottomRight,
23 TopCenter,
24 BottomCenter,
25 LeftCenter,
26 RightCenter,
27 Center,
28 #[cfg(feature = "tray-icon")]
29 TrayLeft,
30 #[cfg(feature = "tray-icon")]
31 TrayBottomLeft,
32 #[cfg(feature = "tray-icon")]
33 TrayRight,
34 #[cfg(feature = "tray-icon")]
35 TrayBottomRight,
36 #[cfg(feature = "tray-icon")]
37 TrayCenter,
38 #[cfg(feature = "tray-icon")]
39 TrayBottomCenter,
40}
41
42pub trait WindowExt {
44 fn move_window(&self, position: Position) -> Result<()>;
48 #[cfg(feature = "tray-icon")]
49 fn move_window_constrained(&self, position: Position) -> Result<()>;
56}
57
58impl<R: Runtime> WindowExt for WebviewWindow<R> {
59 fn move_window(&self, pos: Position) -> Result<()> {
60 self.as_ref().window().move_window(pos)
61 }
62
63 #[cfg(feature = "tray-icon")]
64 fn move_window_constrained(&self, position: Position) -> Result<()> {
65 self.as_ref().window().move_window_constrained(position)
66 }
67}
68
69impl<R: Runtime> WindowExt for Window<R> {
70 #[cfg(feature = "tray-icon")]
71 fn move_window_constrained(&self, position: Position) -> Result<()> {
72 if !matches!(
74 position,
75 Position::TrayLeft
76 | Position::TrayBottomLeft
77 | Position::TrayRight
78 | Position::TrayBottomRight
79 | Position::TrayCenter
80 | Position::TrayBottomCenter
81 ) {
82 return self.move_window(position);
83 }
84
85 let window_position = calculate_position(self, position)?;
86 let monitor = get_monitor_for_tray_icon(self)?;
87 if let Some(monitor) = monitor {
88 let monitor_size = monitor.size();
89 let monitor_position = monitor.position();
90 let window_size = self.outer_size()?;
91
92 let right_border_monitor = monitor_position.x as f64 + monitor_size.width as f64;
93 let left_border_monitor = monitor_position.x as f64;
94 let right_border_window = window_position.x as f64 + window_size.width as f64;
95 let left_border_window = window_position.x as f64;
96
97 let constrained_x = if left_border_window < left_border_monitor {
98 left_border_monitor
99 } else if right_border_window > right_border_monitor {
100 right_border_monitor - window_size.width as f64
101 } else {
102 window_position.x as f64
103 };
104
105 let bottom_border_monitor = monitor_position.y as f64 + monitor_size.height as f64;
106 let top_border_monitor = monitor_position.y as f64;
107 let bottom_border_window = window_position.y as f64 + window_size.height as f64;
108 let top_border_window = window_position.y as f64;
109
110 let constrained_y = if top_border_window < top_border_monitor {
111 top_border_monitor
112 } else if bottom_border_window > bottom_border_monitor {
113 bottom_border_monitor - window_size.height as f64
114 } else {
115 window_position.y as f64
116 };
117
118 self.set_position(PhysicalPosition::new(constrained_x, constrained_y))?;
119 } else {
120 self.set_position(window_position)?;
122 }
123
124 Ok(())
125 }
126
127 fn move_window(&self, pos: Position) -> Result<()> {
128 let position = calculate_position(self, pos)?;
129 self.set_position(position)
130 }
131}
132
133#[cfg(feature = "tray-icon")]
134fn get_monitor_for_tray_icon<R: Runtime>(window: &Window<R>) -> Result<Option<Monitor>> {
136 let tray_position = window
137 .state::<Tray>()
138 .0
139 .lock()
140 .unwrap()
141 .map(|(pos, _)| pos)
142 .unwrap_or_default();
143
144 window.monitor_from_point(tray_position.x, tray_position.y)
145}
146
147fn calculate_position<R: Runtime>(
150 window: &Window<R>,
151 pos: Position,
152) -> Result<PhysicalPosition<i32>> {
153 use Position::*;
154
155 let screen = window.current_monitor()?.ok_or_else(|| {
156 tauri::Error::Io(std::io::Error::other("No monitor found for the window"))
157 })?;
158 let screen_position = screen.position();
161 let screen_size = PhysicalSize::<i32> {
162 width: screen.size().width as i32,
163 height: screen.size().height as i32,
164 };
165 let window_size = PhysicalSize::<i32> {
166 width: window.outer_size()?.width as i32,
167 height: window.outer_size()?.height as i32,
168 };
169 #[cfg(feature = "tray-icon")]
170 let (tray_position, tray_size) = window
171 .state::<Tray>()
172 .0
173 .lock()
174 .unwrap()
175 .map(|(pos, size)| {
176 (
177 Some((pos.x as i32, pos.y as i32)),
178 Some((size.width as i32, size.height as i32)),
179 )
180 })
181 .unwrap_or_default();
182
183 let physical_pos = match pos {
184 TopLeft => *screen_position,
185 TopRight => PhysicalPosition {
186 x: screen_position.x + (screen_size.width - window_size.width),
187 y: screen_position.y,
188 },
189 BottomLeft => PhysicalPosition {
190 x: screen_position.x,
191 y: screen_size.height - (window_size.height - screen_position.y),
192 },
193 BottomRight => PhysicalPosition {
194 x: screen_position.x + (screen_size.width - window_size.width),
195 y: screen_size.height - (window_size.height - screen_position.y),
196 },
197 TopCenter => PhysicalPosition {
198 x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)),
199 y: screen_position.y,
200 },
201 BottomCenter => PhysicalPosition {
202 x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)),
203 y: screen_size.height - (window_size.height - screen_position.y),
204 },
205 LeftCenter => PhysicalPosition {
206 x: screen_position.x,
207 y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2),
208 },
209 RightCenter => PhysicalPosition {
210 x: screen_position.x + (screen_size.width - window_size.width),
211 y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2),
212 },
213 Center => PhysicalPosition {
214 x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)),
215 y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2),
216 },
217 #[cfg(feature = "tray-icon")]
218 TrayLeft => {
219 if let (Some((tray_x, tray_y)), Some((_, _tray_height))) = (tray_position, tray_size) {
220 let y = tray_y - window_size.height;
221 #[cfg(target_os = "windows")]
223 let y = if y < 0 { tray_y + _tray_height } else { y };
224
225 #[cfg(target_os = "macos")]
226 let y = if y < 0 { tray_y } else { y };
227
228 PhysicalPosition { x: tray_x, y }
229 } else {
230 return Err(tauri::Error::Io(std::io::Error::other(
231 "Tray position not set",
232 )));
233 }
234 }
235 #[cfg(feature = "tray-icon")]
236 TrayBottomLeft => {
237 if let Some((tray_x, tray_y)) = tray_position {
238 PhysicalPosition {
239 x: tray_x,
240 y: tray_y,
241 }
242 } else {
243 return Err(tauri::Error::Io(std::io::Error::other(
244 "Tray position not set",
245 )));
246 }
247 }
248 #[cfg(feature = "tray-icon")]
249 TrayRight => {
250 if let (Some((tray_x, tray_y)), Some((tray_width, _tray_height))) =
251 (tray_position, tray_size)
252 {
253 let y = tray_y - window_size.height;
254 #[cfg(target_os = "windows")]
256 let y = if y < 0 { tray_y + _tray_height } else { y };
257
258 #[cfg(target_os = "macos")]
259 let y = if y < 0 { tray_y } else { y };
260
261 PhysicalPosition {
262 x: tray_x + tray_width,
263 y,
264 }
265 } else {
266 return Err(tauri::Error::Io(std::io::Error::other(
267 "Tray position not set",
268 )));
269 }
270 }
271 #[cfg(feature = "tray-icon")]
272 TrayBottomRight => {
273 if let (Some((tray_x, tray_y)), Some((tray_width, _))) = (tray_position, tray_size) {
274 PhysicalPosition {
275 x: tray_x + tray_width,
276 y: tray_y,
277 }
278 } else {
279 return Err(tauri::Error::Io(std::io::Error::other(
280 "Tray position not set",
281 )));
282 }
283 }
284 #[cfg(feature = "tray-icon")]
285 TrayCenter => {
286 if let (Some((tray_x, tray_y)), Some((tray_width, _tray_height))) =
287 (tray_position, tray_size)
288 {
289 let x = tray_x + tray_width / 2 - window_size.width / 2;
290 let y = tray_y - window_size.height;
291 #[cfg(target_os = "windows")]
293 let y = if y < 0 { tray_y + _tray_height } else { y };
294
295 #[cfg(target_os = "macos")]
296 let y = if y < 0 { tray_y } else { y };
297
298 PhysicalPosition { x, y }
299 } else {
300 return Err(tauri::Error::Io(std::io::Error::other(
301 "Tray position not set",
302 )));
303 }
304 }
305 #[cfg(feature = "tray-icon")]
306 TrayBottomCenter => {
307 if let (Some((tray_x, tray_y)), Some((tray_width, _))) = (tray_position, tray_size) {
308 PhysicalPosition {
309 x: tray_x + (tray_width / 2) - (window_size.width / 2),
310 y: tray_y,
311 }
312 } else {
313 return Err(tauri::Error::Io(std::io::Error::other(
314 "Tray position not set",
315 )));
316 }
317 }
318 };
319
320 Ok(physical_pos)
321}