1use optic_core::{Coord2D, CoordOffset, Size2D};
2use winit::dpi::{PhysicalPosition, PhysicalSize};
3use winit::window::{CursorGrabMode, Fullscreen, Window as WinitWindow};
4
5use crate::ScreenInfo;
6
7#[derive(Debug)]
31pub struct Window {
32 inner: Option<std::sync::Arc<WinitWindow>>,
33 prev_cursor_pos: Coord2D,
34 cursor_delta: CoordOffset,
35 prev_position: Coord2D,
36 position_delta: CoordOffset,
37 prev_size: Size2D,
38 cursor_inside: bool,
39 tracking_started: bool,
40 cursor_pos: Coord2D,
41 cursor_visible: bool,
42 cursor_grabbed: bool,
43 cursor_confined: bool,
44 cursor_loopback: bool,
45 min_size: Option<Size2D>,
46 max_size: Option<Size2D>,
47}
48
49impl Window {
50 #[allow(deprecated)]
55 pub fn new(el: &winit::event_loop::EventLoop<()>, title: &str, size: Size2D) -> Self {
56 let attrs = WinitWindow::default_attributes()
57 .with_title(title)
58 .with_inner_size(PhysicalSize::new(size.w, size.h))
59 .with_visible(false);
60 let w = el.create_window(attrs).unwrap();
61 let arc = std::sync::Arc::new(w);
62 let window = Self {
63 inner: Some(arc),
64 prev_cursor_pos: Coord2D::empty(),
65 cursor_delta: CoordOffset::empty(),
66 prev_position: Coord2D::empty(),
67 position_delta: CoordOffset::empty(),
68 prev_size: size,
69 cursor_inside: true,
70 tracking_started: false,
71 cursor_pos: Coord2D::empty(),
72 cursor_visible: true,
73 cursor_grabbed: false,
74 cursor_confined: false,
75 cursor_loopback: false,
76 min_size: None,
77 max_size: None,
78 };
79 window
80 }
81
82 #[allow(deprecated)]
86 pub fn new_transparent(el: &winit::event_loop::EventLoop<()>, title: &str, size: Size2D) -> Self {
87 let attrs = WinitWindow::default_attributes()
88 .with_title(title)
89 .with_inner_size(PhysicalSize::new(size.w, size.h))
90 .with_visible(false)
91 .with_transparent(true);
92 let w = el.create_window(attrs).unwrap();
93 let arc = std::sync::Arc::new(w);
94 let window = Self {
95 inner: Some(arc),
96 prev_cursor_pos: Coord2D::empty(),
97 cursor_delta: CoordOffset::empty(),
98 prev_position: Coord2D::empty(),
99 position_delta: CoordOffset::empty(),
100 prev_size: size,
101 cursor_inside: true,
102 tracking_started: false,
103 cursor_pos: Coord2D::empty(),
104 cursor_visible: true,
105 cursor_grabbed: false,
106 cursor_confined: false,
107 cursor_loopback: false,
108 min_size: None,
109 max_size: None,
110 };
111 window
112 }
113
114 pub fn close(&mut self) {
116 self.inner = None;
117 }
118
119 pub fn is_closed(&self) -> bool {
121 self.inner.is_none()
122 }
123
124 pub fn is_running(&self) -> bool {
126 self.inner.is_some()
127 }
128
129 pub fn raw_handle(&self) -> Option<raw_window_handle::RawWindowHandle> {
133 use raw_window_handle::HasWindowHandle;
134 self.inner.as_ref().map(|w| w.window_handle().unwrap().as_raw())
135 }
136
137 pub fn raw_display_handle(&self) -> Option<raw_window_handle::RawDisplayHandle> {
139 use raw_window_handle::HasDisplayHandle;
140 self.inner.as_ref().map(|w| w.display_handle().unwrap().as_raw())
141 }
142
143 pub fn id(&self) -> Option<winit::window::WindowId> {
145 self.inner.as_ref().map(|w| w.id())
146 }
147
148 pub fn size(&self) -> Size2D {
152 self.inner.as_ref().map_or(self.prev_size, |w| {
153 let s = w.inner_size();
154 Size2D::from(s.width, s.height)
155 })
156 }
157
158 pub fn set_size(&self, size: Size2D) {
162 if let Some(ref w) = self.inner {
163 let _ = w.request_inner_size(PhysicalSize::new(size.w, size.h));
164 }
165 }
166
167 pub fn prev_size(&self) -> Size2D {
169 self.prev_size
170 }
171
172 pub fn min_size(&self) -> Option<Size2D> {
174 self.min_size
175 }
176
177 pub fn set_min_size(&mut self, size: Option<Size2D>) {
179 self.min_size = size;
180 if let Some(ref w) = self.inner {
181 w.set_min_inner_size(size.map(|s| PhysicalSize::new(s.w, s.h)));
182 }
183 }
184
185 pub fn max_size(&self) -> Option<Size2D> {
187 self.max_size
188 }
189
190 pub fn set_max_size(&mut self, size: Option<Size2D>) {
192 self.max_size = size;
193 if let Some(ref w) = self.inner {
194 w.set_max_inner_size(size.map(|s| PhysicalSize::new(s.w, s.h)));
195 }
196 }
197
198 pub fn resizable(&self) -> bool {
200 self.inner.as_ref().map_or(true, |w| w.is_resizable())
201 }
202
203 pub fn set_resizable(&self, enable: bool) {
205 if let Some(ref w) = self.inner {
206 w.set_resizable(enable);
207 }
208 }
209
210 pub fn position(&self) -> Coord2D {
214 self.inner.as_ref().and_then(|w| {
215 w.outer_position().ok().map(|p| Coord2D::from(p.x as f64, p.y as f64))
216 }).unwrap_or(self.prev_position)
217 }
218
219 pub fn set_position(&self, pos: Coord2D) {
221 if let Some(ref w) = self.inner {
222 let _ = w.set_outer_position(PhysicalPosition::new(pos.x as i32, pos.y as i32));
223 }
224 }
225
226 pub fn center_on_screen(&self) {
228 if let Some(ref w) = self.inner {
229 if let Some(monitor) = w.current_monitor() {
230 let mon_size = monitor.size();
231 let win_size = w.outer_size();
232 let x = (mon_size.width.saturating_sub(win_size.width)) / 2;
233 let y = (mon_size.height.saturating_sub(win_size.height)) / 2;
234 let _ = w.set_outer_position(PhysicalPosition::new(x as i32, y as i32));
235 }
236 }
237 }
238
239 pub fn prev_position(&self) -> Coord2D {
241 self.prev_position
242 }
243
244 pub fn position_delta(&mut self) -> CoordOffset {
246 let d = self.position_delta;
247 self.position_delta = CoordOffset::empty();
248 d
249 }
250
251 pub fn title(&self) -> String {
255 self.inner.as_ref().map_or(String::new(), |w| w.title())
256 }
257
258 pub fn set_title(&self, title: &str) {
260 if let Some(ref w) = self.inner {
261 w.set_title(title);
262 }
263 }
264
265 pub fn is_fullscreen(&self) -> bool {
269 self.inner.as_ref().and_then(|w| w.fullscreen()).is_some()
270 }
271
272 pub fn set_fullscreen(&self, enable: bool) {
274 if let Some(ref w) = self.inner {
275 if enable {
276 w.set_fullscreen(Some(Fullscreen::Borderless(None)));
277 } else {
278 w.set_fullscreen(None);
279 }
280 }
281 }
282
283 pub fn toggle_fullscreen(&self) {
285 self.set_fullscreen(!self.is_fullscreen());
286 }
287
288 pub fn is_visible(&self) -> bool {
292 self.inner.as_ref().and_then(|w| w.is_visible()).unwrap_or(false)
293 }
294
295 pub fn set_visible(&self, visible: bool) {
297 if let Some(ref w) = self.inner {
298 w.set_visible(visible);
299 }
300 }
301
302 pub fn is_minimized(&self) -> bool {
304 self.inner.as_ref().and_then(|w| w.is_minimized()).unwrap_or(false)
305 }
306
307 pub fn minimize(&self) {
309 if let Some(ref w) = self.inner {
310 w.set_minimized(true);
311 }
312 }
313
314 pub fn restore(&self) {
316 if let Some(ref w) = self.inner {
317 w.set_minimized(false);
318 }
319 }
320
321 pub fn is_maximized(&self) -> bool {
323 self.inner.as_ref().map_or(false, |w| w.is_maximized())
324 }
325
326 pub fn maximize(&self) {
328 if let Some(ref w) = self.inner {
329 w.set_maximized(true);
330 }
331 }
332
333 pub fn unmaximize(&self) {
335 if let Some(ref w) = self.inner {
336 w.set_maximized(false);
337 }
338 }
339
340 pub fn has_focus(&self) -> bool {
342 self.inner.as_ref().map_or(false, |w| w.has_focus())
343 }
344
345 pub fn focus(&self) {
347 if let Some(ref w) = self.inner {
348 w.focus_window();
349 }
350 }
351
352 pub fn request_redraw(&self) {
356 if let Some(ref w) = self.inner {
357 w.request_redraw();
358 }
359 }
360
361 pub fn screen_info(&self) -> Option<ScreenInfo> {
365 self.inner.as_ref().and_then(|w| {
366 w.current_monitor().map(|m| ScreenInfo::from_handle(&m))
367 })
368 }
369
370 pub fn cursor_pos(&self) -> Coord2D {
374 self.cursor_pos
375 }
376
377 pub fn set_cursor_pos(&mut self, pos: Coord2D) {
379 self.cursor_pos = pos;
380 if let Some(ref w) = self.inner {
381 let _ = w.set_cursor_position(PhysicalPosition::new(pos.x, pos.y));
382 }
383 }
384
385 pub fn cursor_delta(&self) -> CoordOffset {
389 self.cursor_delta
390 }
391
392 pub fn cursor_pos_normalized(&self) -> Coord2D {
394 let sz = self.size();
395 if sz.w == 0 || sz.h == 0 {
396 return Coord2D::empty();
397 }
398 Coord2D::from(self.cursor_pos.x / sz.w as f64, 1.0 - self.cursor_pos.y / sz.h as f64)
399 }
400
401 pub fn is_cursor_inside(&self) -> bool {
403 self.cursor_inside
404 }
405
406 pub fn is_cursor_visible(&self) -> bool {
408 self.cursor_visible
409 }
410
411 pub fn set_cursor_visible(&mut self, visible: bool) {
413 self.cursor_visible = visible;
414 if let Some(ref w) = self.inner {
415 w.set_cursor_visible(visible);
416 }
417 }
418
419 pub fn toggle_cursor_visible(&mut self) {
421 self.set_cursor_visible(!self.cursor_visible);
422 }
423
424 pub fn is_cursor_grabbed(&self) -> bool {
426 self.cursor_grabbed
427 }
428
429 pub fn set_cursor_grab(&mut self, grab: bool) -> Result<(), ()> {
432 let result = match self.inner.as_ref() {
433 Some(w) => w.set_cursor_grab(if grab { CursorGrabMode::Locked } else { CursorGrabMode::None })
434 .map_err(|_| ()),
435 None => Err(()),
436 };
437 if result.is_ok() {
438 self.cursor_grabbed = grab;
439 }
440 result
441 }
442
443 pub fn toggle_cursor_grab(&mut self) {
445 let _ = self.set_cursor_grab(!self.cursor_grabbed);
446 }
447
448 pub fn is_cursor_confined(&self) -> bool {
450 self.cursor_confined
451 }
452
453 pub fn set_cursor_confine(&mut self, confine: bool) -> Result<(), ()> {
455 let result = match self.inner.as_ref() {
456 Some(w) => w.set_cursor_grab(if confine { CursorGrabMode::Confined } else { CursorGrabMode::None })
457 .map_err(|_| ()),
458 None => Err(()),
459 };
460 if result.is_ok() {
461 self.cursor_confined = confine;
462 }
463 result
464 }
465
466 pub fn toggle_cursor_confine(&mut self) {
468 let _ = self.set_cursor_confine(!self.cursor_confined);
469 }
470
471 pub fn is_cursor_loopback(&self) -> bool {
473 self.cursor_loopback
474 }
475
476 pub fn set_cursor_loopback(&mut self, loopback: bool) {
479 self.cursor_loopback = loopback;
480 }
481
482 pub fn update_frame(&mut self) {
489 let new_pos = self.position();
490 if !self.tracking_started {
491 self.prev_cursor_pos = self.cursor_pos;
492 self.prev_position = new_pos;
493 self.prev_size = self.size();
494 self.position_delta = CoordOffset::empty();
495 self.cursor_delta = CoordOffset::empty();
496 self.tracking_started = true;
497 } else {
498 let raw = self.cursor_pos - self.prev_cursor_pos;
499 self.cursor_delta = CoordOffset { x: raw.x, y: -raw.y };
500 self.prev_cursor_pos = self.cursor_pos;
501 self.position_delta = self.position_delta + (new_pos - self.prev_position);
502 self.prev_position = new_pos;
503 self.prev_size = self.size();
504 }
505
506 if self.cursor_loopback {
507 let sz = self.size();
508 if sz.w > 0 && sz.h > 0 {
509 let mut wrapped = false;
510 let mut new_pos = self.cursor_pos;
511 if new_pos.x <= 0.0 { new_pos.x = (sz.w - 1) as f64; wrapped = true; }
512 else if new_pos.x >= (sz.w - 1) as f64 { new_pos.x = 0.0; wrapped = true; }
513 if new_pos.y <= 0.0 { new_pos.y = (sz.h - 1) as f64; wrapped = true; }
514 else if new_pos.y >= (sz.h - 1) as f64 { new_pos.y = 0.0; wrapped = true; }
515 if wrapped {
516 self.set_cursor_pos(new_pos);
517 self.prev_cursor_pos = self.cursor_pos;
518 }
519 }
520 }
521 }
522
523 #[doc(hidden)]
526 pub fn notify_cursor_moved(&mut self, pos: Coord2D) {
527 self.cursor_pos = pos;
528 }
529
530 #[doc(hidden)]
531 pub fn notify_cursor_inside(&mut self, inside: bool) {
532 self.cursor_inside = inside;
533 }
534}