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