1use std::sync::Arc;
2
3#[cfg(feature = "ui")]
4use rfd::MessageDialog;
5
6use crate::thecontext::TheCursorIcon;
7use crate::thewinitbackend::TheWinitBackend;
8
9#[cfg(target_os = "macos")]
10use winit::platform::macos::WindowExtMacOS;
11
12use crate::prelude::*;
13use web_time::{Duration, Instant};
14use winit::{
15 application::ApplicationHandler,
16 dpi::{LogicalSize, PhysicalPosition, PhysicalSize},
17 event::{
18 DeviceEvent, DeviceId, ElementState, MouseButton, MouseScrollDelta, StartCause, Touch,
19 TouchPhase, WindowEvent,
20 },
21 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
22 keyboard::{Key, KeyCode, ModifiersState, NamedKey},
23 window::{Icon, Window, WindowAttributes, WindowId},
24};
25
26#[inline]
28fn is_accel_mods(m: &ModifiersState) -> bool {
29 #[cfg(target_os = "macos")]
30 {
31 m.super_key() || m.alt_key() || m.control_key()
33 }
34 #[cfg(not(target_os = "macos"))]
35 {
36 m.super_key() || m.control_key()
38 }
39}
40
41fn accel_physical_to_ascii(code: KeyCode, shift: bool) -> Option<char> {
43 let (base, shifted) = match code {
44 KeyCode::KeyA => ('a', 'A'),
46 KeyCode::KeyB => ('b', 'B'),
47 KeyCode::KeyC => ('c', 'C'),
48 KeyCode::KeyD => ('d', 'D'),
49 KeyCode::KeyE => ('e', 'E'),
50 KeyCode::KeyF => ('f', 'F'),
51 KeyCode::KeyG => ('g', 'G'),
52 KeyCode::KeyH => ('h', 'H'),
53 KeyCode::KeyI => ('i', 'I'),
54 KeyCode::KeyJ => ('j', 'J'),
55 KeyCode::KeyK => ('k', 'K'),
56 KeyCode::KeyL => ('l', 'L'),
57 KeyCode::KeyM => ('m', 'M'),
58 KeyCode::KeyN => ('n', 'N'),
59 KeyCode::KeyO => ('o', 'O'),
60 KeyCode::KeyP => ('p', 'P'),
61 KeyCode::KeyQ => ('q', 'Q'),
62 KeyCode::KeyR => ('r', 'R'),
63 KeyCode::KeyS => ('s', 'S'),
64 KeyCode::KeyT => ('t', 'T'),
65 KeyCode::KeyU => ('u', 'U'),
66 KeyCode::KeyV => ('v', 'V'),
67 KeyCode::KeyW => ('w', 'W'),
68 KeyCode::KeyX => ('x', 'X'),
69 KeyCode::KeyY => ('y', 'Y'),
70 KeyCode::KeyZ => ('z', 'Z'),
71 KeyCode::Digit0 => ('0', ')'),
73 KeyCode::Digit1 => ('1', '!'),
74 KeyCode::Digit2 => ('2', '@'),
75 KeyCode::Digit3 => ('3', '#'),
76 KeyCode::Digit4 => ('4', '$'),
77 KeyCode::Digit5 => ('5', '%'),
78 KeyCode::Digit6 => ('6', '^'),
79 KeyCode::Digit7 => ('7', '&'),
80 KeyCode::Digit8 => ('8', '*'),
81 KeyCode::Digit9 => ('9', '('),
82 KeyCode::Minus => ('-', '_'),
84 KeyCode::Equal => ('=', '+'),
85 KeyCode::BracketLeft => ('[', '{'),
86 KeyCode::BracketRight => (']', '}'),
87 KeyCode::Semicolon => (';', ':'),
88 KeyCode::Quote => ('\'', '"'),
89 KeyCode::Comma => (',', '<'),
90 KeyCode::Period => ('.', '>'),
91 KeyCode::Slash => ('/', '?'),
92 KeyCode::Backquote => ('`', '~'),
93 KeyCode::Backslash => ('\\', '|'),
94 KeyCode::IntlBackslash => ('\\', '|'),
95 KeyCode::Space => (' ', ' '),
97 _ => return None,
98 };
99 Some(if shift { shifted } else { base })
100}
101
102fn translate_coord_to_local(x: f32, y: f32, scale_factor: f32) -> (f32, f32) {
103 (x / scale_factor, y / scale_factor)
104}
105
106struct TheWinitContext {
107 window: Arc<Window>,
108 ctx: TheContext,
109 ui_frame: Vec<u8>,
110 backend: TheWinitBackend,
111}
112
113impl TheWinitContext {
114 fn from_window(window: Arc<Window>) -> Self {
115 #[cfg(not(target_os = "macos"))]
116 let scale_factor = 1.0;
117 #[cfg(target_os = "macos")]
119 let scale_factor = window.scale_factor() as f32;
120
121 let size = window.inner_size();
122
123 #[cfg(target_arch = "wasm32")]
126 let (width, height) = {
127 let wasm_scale = window.scale_factor() as f32;
128 (
129 (size.width as f32 / wasm_scale) as usize,
130 (size.height as f32 / wasm_scale) as usize,
131 )
132 };
133 #[cfg(not(target_arch = "wasm32"))]
134 let (width, height) = (size.width as usize, size.height as usize);
135
136 let ctx = TheContext::new(width, height, scale_factor);
147
148 let ui_frame = vec![0; (width * height * 4) as usize];
149
150 let backend = TheWinitBackend::new(window.clone(), width, height, scale_factor);
151
152 TheWinitContext {
153 window,
154 ctx,
155 ui_frame,
156 backend,
157 }
158 }
159}
160
161struct TheWinitApp {
162 args: Option<Vec<String>>,
163 ctx: Option<TheWinitContext>,
164 app: Box<dyn TheTrait>,
165
166 mods: ModifiersState,
167 target_frame_time: Duration,
168 next_frame_time: Instant,
169 last_cursor_pos: Option<(f32, f32)>,
170 left_mouse_down: bool,
171 has_changes: bool,
172
173 #[cfg(feature = "ui")]
174 ui: TheUI,
175}
176
177impl TheWinitApp {
178 fn new(args: Option<Vec<String>>, app: Box<dyn TheTrait>) -> Self {
179 let fps = app.target_fps();
180
181 TheWinitApp {
182 args,
183 ctx: None,
184 app,
185 mods: ModifiersState::empty(),
186 target_frame_time: Duration::from_secs_f64(1.0 / fps),
187 next_frame_time: Instant::now(),
188 last_cursor_pos: None,
189 left_mouse_down: false,
190 has_changes: false,
191 #[cfg(feature = "ui")]
192 ui: TheUI::new(),
193 }
194 }
195
196 fn create_window(&mut self, event_loop: &ActiveEventLoop) -> Option<Arc<Window>> {
197 let window_title = self.app.window_title();
198 let mut icon: Option<Icon> = None;
199 if let Some(window_icon) = self.app.window_icon() {
200 icon = Icon::from_rgba(window_icon.0, window_icon.1, window_icon.2).ok();
201 }
202
203 let (width, height) = self.app.default_window_size();
204 let size = LogicalSize::new(width as f64, height as f64);
205 let (min_width, min_height) = self.app.min_window_size();
206 let min_size = LogicalSize::new(min_width as f64, min_height as f64);
207
208 let window_attributes = WindowAttributes::default()
209 .with_title(window_title)
210 .with_inner_size(size)
211 .with_min_inner_size(min_size)
212 .with_window_icon(icon); let window_attributes = if let Some((x, y)) = self.app.default_window_position() {
215 window_attributes.with_position(PhysicalPosition::new(x, y))
216 } else {
217 window_attributes
218 };
219
220 #[cfg(target_arch = "wasm32")]
221 let window_attributes = {
222 use winit::platform::web::WindowAttributesExtWebSys;
223
224 window_attributes.with_append(true)
225 };
226
227 let window = event_loop.create_window(window_attributes).unwrap();
228
229 Some(Arc::new(window))
230 }
231
232 fn init_context(&mut self, window: Arc<Window>) -> TheWinitContext {
233 let mut ctx = TheWinitContext::from_window(window);
234
235 #[cfg(feature = "ui")]
236 {
237 self.ui.init(&mut ctx.ctx);
238
239 self.ui.canvas.root = true;
240 self.ui.canvas.set_dim(
241 TheDim::new(0, 0, ctx.ctx.width as i32, ctx.ctx.height as i32),
242 &mut ctx.ctx,
243 );
244
245 self.app.init_ui(&mut self.ui, &mut ctx.ctx);
246 self.ui
247 .canvas
248 .layout(ctx.ctx.width as i32, ctx.ctx.height as i32, &mut ctx.ctx);
249 }
250
251 #[cfg(feature = "i18n")]
252 ctx.ctx.load_system_fonts(self.app.fonts_to_load());
253
254 self.app.init(&mut ctx.ctx);
255
256 if let Some(args) = self.args.take() {
258 self.app.set_cmd_line_args(args, &mut ctx.ctx);
259 }
260
261 ctx
262 }
263
264 fn render(&mut self) {
265 let Some(ctx) = &mut self.ctx else {
266 return;
267 };
268
269 if ctx.ctx.width == 0 || ctx.ctx.height == 0 {
270 return;
271 }
272
273 #[cfg(feature = "ui")]
274 self.app.pre_ui(&mut ctx.ctx);
275
276 #[cfg(feature = "ui")]
277 self.ui.draw(&mut ctx.ui_frame, &mut ctx.ctx);
278
279 self.app.draw(&mut ctx.ui_frame, &mut ctx.ctx);
282
283 ctx.backend.present(
284 &ctx.window,
285 &ctx.ui_frame,
286 ctx.ctx.width,
287 ctx.ctx.height,
288 ctx.ctx.scale_factor,
289 );
290
291 #[cfg(feature = "ui")]
292 self.app.post_ui(&mut ctx.ctx);
293 }
294
295 fn process_updates(&mut self) -> bool {
296 let Some(ctx) = &mut self.ctx else {
297 return false;
298 };
299
300 let mut wants_redraw = false;
301
302 let current_has_changes = self.app.has_changes();
303 if current_has_changes != self.has_changes {
304 self.has_changes = current_has_changes;
305 #[cfg(target_os = "macos")]
306 ctx.window.set_document_edited(current_has_changes);
307 }
308
309 #[cfg(feature = "ui")]
310 if self.ui.update(&mut ctx.ctx) {
311 wants_redraw = true;
312 }
313
314 #[cfg(feature = "ui")]
315 if self.app.update_ui(&mut self.ui, &mut ctx.ctx) {
316 wants_redraw = true;
317 }
318
319 if self.app.update(&mut ctx.ctx) {
320 wants_redraw = true;
321 }
322
323 wants_redraw
324 }
325
326 fn resize(&mut self, size: PhysicalSize<u32>) {
327 let Some(ctx) = &mut self.ctx else {
328 return;
329 };
330
331 if size.width != 0 && size.height != 0 {
332 let scale_factor = ctx.window.scale_factor() as f32;
336
337 #[cfg(all(not(target_os = "macos"), not(target_arch = "wasm32")))]
339 let (effective_scale, width, height) = if scale_factor.fract() != 0.0 {
340 (1.0_f32, size.width, size.height)
341 } else {
342 (
343 scale_factor,
344 (size.width as f32 / scale_factor).round() as u32,
345 (size.height as f32 / scale_factor).round() as u32,
346 )
347 };
348 #[cfg(any(target_os = "macos", target_arch = "wasm32"))]
350 let (effective_scale, width, height) = (
351 scale_factor,
352 (size.width as f32 / scale_factor).round() as u32,
353 (size.height as f32 / scale_factor).round() as u32,
354 );
355
356 ctx.ctx.scale_factor = effective_scale;
357
358 ctx.backend
359 .resize(&ctx.window, size, width, height, scale_factor);
360
361 ctx.ctx.width = width as usize;
366 ctx.ctx.height = height as usize;
367
368 ctx.ui_frame.resize((width * height * 4) as usize, 0);
369 #[cfg(feature = "ui")]
372 self.ui
373 .canvas
374 .set_dim(TheDim::new(0, 0, width as i32, height as i32), &mut ctx.ctx);
375 #[cfg(feature = "ui")]
376 ctx.ctx.ui.send(TheEvent::Resize);
377
378 self.app.window_resized(width as usize, height as usize);
379
380 ctx.window.request_redraw();
381 }
382 }
383}
384
385impl ApplicationHandler for TheWinitApp {
386 fn new_events(&mut self, _: &ActiveEventLoop, _: StartCause) {}
387
388 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
389 if self.ctx.is_none() {
390 if let Some(window) = self.create_window(event_loop) {
391 self.ctx = Some(self.init_context(window));
392 if let Some(ctx) = &mut self.ctx {
394 ctx.ctx.set_cursor_icon(TheCursorIcon::Default);
395 ctx.window.set_cursor_visible(true);
396 }
397 }
398 }
399 }
400
401 fn window_event(
402 &mut self,
403 event_loop: &ActiveEventLoop,
404 _window_id: WindowId,
405 event: WindowEvent,
406 ) {
407 match event {
408 WindowEvent::RedrawRequested => {
409 self.render();
410 }
411 WindowEvent::CloseRequested => {
412 if !self.app.closing() {
413 #[cfg(feature = "ui")]
414 {
415 if self.app.has_changes() {
416 let result = MessageDialog::new()
417 .set_title("Unsaved Changes")
418 .set_description(
419 "You have unsaved changes. Are you sure you want to quit?",
420 )
421 .set_buttons(rfd::MessageButtons::YesNo)
422 .show();
423
424 if result == rfd::MessageDialogResult::Yes {
425 event_loop.exit();
426 }
427 } else {
428 event_loop.exit();
429 }
430 }
431 #[cfg(not(feature = "ui"))]
432 {
433 event_loop.exit();
434 }
435 }
436 }
437 WindowEvent::Resized(size) => {
438 self.resize(size);
439 }
440 WindowEvent::Moved(position) => {
441 self.app.window_moved(position.x, position.y);
442 }
443 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
444 if let Some(ctx) = &mut self.ctx {
445 ctx.ctx.scale_factor = scale_factor as f32;
446 let size = ctx.window.inner_size();
447 self.resize(size);
448 }
449 }
450 event => {
451 let user_driven_event = matches!(
452 &event,
453 WindowEvent::KeyboardInput { .. }
454 | WindowEvent::ModifiersChanged(_)
455 | WindowEvent::CursorMoved { .. }
456 | WindowEvent::Touch(_)
457 | WindowEvent::MouseInput { .. }
458 | WindowEvent::MouseWheel { .. }
459 | WindowEvent::DroppedFile(_)
460 );
461 if user_driven_event {
462 self.next_frame_time = Instant::now();
464 }
465
466 let Some(ctx) = &mut self.ctx else {
467 return;
468 };
469
470 match event {
471 WindowEvent::KeyboardInput {
472 event: key_event, ..
473 } => {
474 if key_event.state == ElementState::Pressed {
475 let key = match &key_event.logical_key {
476 Key::Named(NamedKey::Delete) | Key::Named(NamedKey::Backspace) => {
477 Some(TheKeyCode::Delete)
478 }
479 Key::Named(NamedKey::Home) => Some(TheKeyCode::Home),
480 Key::Named(NamedKey::End) => Some(TheKeyCode::End),
481 Key::Named(NamedKey::ArrowUp) => Some(TheKeyCode::Up),
482 Key::Named(NamedKey::ArrowRight) => Some(TheKeyCode::Right),
483 Key::Named(NamedKey::ArrowDown) => Some(TheKeyCode::Down),
484 Key::Named(NamedKey::ArrowLeft) => Some(TheKeyCode::Left),
485 Key::Named(NamedKey::Space) => Some(TheKeyCode::Space),
486 Key::Named(NamedKey::Tab) => Some(TheKeyCode::Tab),
487 Key::Named(NamedKey::Enter) => Some(TheKeyCode::Return),
488 Key::Named(NamedKey::Escape) => Some(TheKeyCode::Escape),
489 Key::Character(str) => {
490 if is_accel_mods(&self.mods) {
492 if let winit::keyboard::PhysicalKey::Code(code) =
493 key_event.physical_key
494 {
495 if let Some(ch) =
496 accel_physical_to_ascii(code, self.mods.shift_key())
497 {
498 #[cfg(feature = "ui")]
499 if self.ui.key_down(Some(ch), None, &mut ctx.ctx) {
500 ctx.window.request_redraw();
501 }
502 if self.app.key_down(Some(ch), None, &mut ctx.ctx) {
503 ctx.window.request_redraw();
504 }
505 return;
506 }
507 }
508 }
509 if str.is_ascii() {
510 for ch in str.chars() {
511 #[cfg(feature = "ui")]
512 if self.ui.key_down(Some(ch), None, &mut ctx.ctx) {
513 ctx.window.request_redraw();
514 }
515 if self.app.key_down(Some(ch), None, &mut ctx.ctx) {
516 ctx.window.request_redraw();
517 }
518 }
519 }
520 None
521 }
522 _ => None,
523 };
524 if key.is_some() {
525 #[cfg(feature = "ui")]
526 if self.ui.key_down(None, key.clone(), &mut ctx.ctx) {
527 ctx.window.request_redraw();
528 }
529 if self.app.key_down(None, key, &mut ctx.ctx) {
530 ctx.window.request_redraw();
531 }
532 }
533
534 if ctx.ctx.cursor_changed() {
536 let cursor_icon = match ctx.ctx.cursor_icon() {
537 TheCursorIcon::Default => winit::window::CursorIcon::Default,
538 TheCursorIcon::Crosshair => {
539 winit::window::CursorIcon::Crosshair
540 }
541 TheCursorIcon::Hand => winit::window::CursorIcon::Pointer,
542 TheCursorIcon::Arrow => winit::window::CursorIcon::Default,
543 TheCursorIcon::Text => winit::window::CursorIcon::Text,
544 TheCursorIcon::Wait => winit::window::CursorIcon::Wait,
545 TheCursorIcon::Help => winit::window::CursorIcon::Help,
546 TheCursorIcon::Progress => winit::window::CursorIcon::Progress,
547 TheCursorIcon::NotAllowed => {
548 winit::window::CursorIcon::NotAllowed
549 }
550 TheCursorIcon::ContextMenu => {
551 winit::window::CursorIcon::ContextMenu
552 }
553 TheCursorIcon::Cell => winit::window::CursorIcon::Cell,
554 TheCursorIcon::VerticalText => {
555 winit::window::CursorIcon::VerticalText
556 }
557 TheCursorIcon::Alias => winit::window::CursorIcon::Alias,
558 TheCursorIcon::Copy => winit::window::CursorIcon::Copy,
559 TheCursorIcon::NoDrop => winit::window::CursorIcon::NoDrop,
560 TheCursorIcon::Grab => winit::window::CursorIcon::Grab,
561 TheCursorIcon::Grabbing => winit::window::CursorIcon::Grabbing,
562 TheCursorIcon::AllScroll => {
563 winit::window::CursorIcon::AllScroll
564 }
565 TheCursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn,
566 TheCursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut,
567 TheCursorIcon::EResize => winit::window::CursorIcon::EResize,
568 TheCursorIcon::NResize => winit::window::CursorIcon::NResize,
569 TheCursorIcon::NEResize => winit::window::CursorIcon::NeResize,
570 TheCursorIcon::NWResize => winit::window::CursorIcon::NwResize,
571 TheCursorIcon::SResize => winit::window::CursorIcon::SResize,
572 TheCursorIcon::SEResize => winit::window::CursorIcon::SeResize,
573 TheCursorIcon::SWResize => winit::window::CursorIcon::SwResize,
574 TheCursorIcon::WResize => winit::window::CursorIcon::WResize,
575 TheCursorIcon::EWResize => winit::window::CursorIcon::EwResize,
576 TheCursorIcon::NSResize => winit::window::CursorIcon::NsResize,
577 TheCursorIcon::NESWResize => {
578 winit::window::CursorIcon::NeswResize
579 }
580 TheCursorIcon::NWSEResize => {
581 winit::window::CursorIcon::NwseResize
582 }
583 TheCursorIcon::ColResize => {
584 winit::window::CursorIcon::ColResize
585 }
586 TheCursorIcon::RowResize => {
587 winit::window::CursorIcon::RowResize
588 }
589 };
590 ctx.window.set_cursor(cursor_icon);
591 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
592 ctx.ctx.reset_cursor_changed();
593 }
594 if ctx.ctx.cursor_visible_changed() {
595 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
596 ctx.ctx.reset_cursor_visible_changed();
597 }
598 }
599 if key_event.state == ElementState::Released {
600 let key = match &key_event.logical_key {
601 Key::Named(NamedKey::Delete) | Key::Named(NamedKey::Backspace) => {
602 Some(TheKeyCode::Delete)
603 }
604 Key::Named(NamedKey::Home) => Some(TheKeyCode::Home),
605 Key::Named(NamedKey::End) => Some(TheKeyCode::End),
606 Key::Named(NamedKey::ArrowUp) => Some(TheKeyCode::Up),
607 Key::Named(NamedKey::ArrowRight) => Some(TheKeyCode::Right),
608 Key::Named(NamedKey::ArrowDown) => Some(TheKeyCode::Down),
609 Key::Named(NamedKey::ArrowLeft) => Some(TheKeyCode::Left),
610 Key::Named(NamedKey::Space) => Some(TheKeyCode::Space),
611 Key::Named(NamedKey::Tab) => Some(TheKeyCode::Tab),
612 Key::Named(NamedKey::Enter) => Some(TheKeyCode::Return),
613 Key::Named(NamedKey::Escape) => Some(TheKeyCode::Escape),
614 Key::Character(str) => {
615 if is_accel_mods(&self.mods) {
617 if let winit::keyboard::PhysicalKey::Code(code) =
618 key_event.physical_key
619 {
620 if let Some(ch) =
621 accel_physical_to_ascii(code, self.mods.shift_key())
622 {
623 #[cfg(feature = "ui")]
624 if self.ui.key_up(Some(ch), None, &mut ctx.ctx) {
625 ctx.window.request_redraw();
626 }
627 if self.app.key_up(Some(ch), None, &mut ctx.ctx) {
628 ctx.window.request_redraw();
629 }
630 return;
631 }
632 }
633 }
634 if str.is_ascii() {
635 for ch in str.chars() {
636 #[cfg(feature = "ui")]
637 if self.ui.key_up(Some(ch), None, &mut ctx.ctx) {
638 ctx.window.request_redraw();
639 }
640 if self.app.key_up(Some(ch), None, &mut ctx.ctx) {
641 ctx.window.request_redraw();
642 }
643 }
644 }
645 None
646 }
647 _ => None,
648 };
649 if key.is_some() {
650 #[cfg(feature = "ui")]
651 if self.ui.key_up(None, key.clone(), &mut ctx.ctx) {
652 ctx.window.request_redraw();
653 }
654 if self.app.key_up(None, key, &mut ctx.ctx) {
655 ctx.window.request_redraw();
656 }
657 }
658
659 if ctx.ctx.cursor_changed() {
661 let cursor_icon = match ctx.ctx.cursor_icon() {
662 TheCursorIcon::Default => winit::window::CursorIcon::Default,
663 TheCursorIcon::Crosshair => {
664 winit::window::CursorIcon::Crosshair
665 }
666 TheCursorIcon::Hand => winit::window::CursorIcon::Pointer,
667 TheCursorIcon::Arrow => winit::window::CursorIcon::Default,
668 TheCursorIcon::Text => winit::window::CursorIcon::Text,
669 TheCursorIcon::Wait => winit::window::CursorIcon::Wait,
670 TheCursorIcon::Help => winit::window::CursorIcon::Help,
671 TheCursorIcon::Progress => winit::window::CursorIcon::Progress,
672 TheCursorIcon::NotAllowed => {
673 winit::window::CursorIcon::NotAllowed
674 }
675 TheCursorIcon::ContextMenu => {
676 winit::window::CursorIcon::ContextMenu
677 }
678 TheCursorIcon::Cell => winit::window::CursorIcon::Cell,
679 TheCursorIcon::VerticalText => {
680 winit::window::CursorIcon::VerticalText
681 }
682 TheCursorIcon::Alias => winit::window::CursorIcon::Alias,
683 TheCursorIcon::Copy => winit::window::CursorIcon::Copy,
684 TheCursorIcon::NoDrop => winit::window::CursorIcon::NoDrop,
685 TheCursorIcon::Grab => winit::window::CursorIcon::Grab,
686 TheCursorIcon::Grabbing => winit::window::CursorIcon::Grabbing,
687 TheCursorIcon::AllScroll => {
688 winit::window::CursorIcon::AllScroll
689 }
690 TheCursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn,
691 TheCursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut,
692 TheCursorIcon::EResize => winit::window::CursorIcon::EResize,
693 TheCursorIcon::NResize => winit::window::CursorIcon::NResize,
694 TheCursorIcon::NEResize => winit::window::CursorIcon::NeResize,
695 TheCursorIcon::NWResize => winit::window::CursorIcon::NwResize,
696 TheCursorIcon::SResize => winit::window::CursorIcon::SResize,
697 TheCursorIcon::SEResize => winit::window::CursorIcon::SeResize,
698 TheCursorIcon::SWResize => winit::window::CursorIcon::SwResize,
699 TheCursorIcon::WResize => winit::window::CursorIcon::WResize,
700 TheCursorIcon::EWResize => winit::window::CursorIcon::EwResize,
701 TheCursorIcon::NSResize => winit::window::CursorIcon::NsResize,
702 TheCursorIcon::NESWResize => {
703 winit::window::CursorIcon::NeswResize
704 }
705 TheCursorIcon::NWSEResize => {
706 winit::window::CursorIcon::NwseResize
707 }
708 TheCursorIcon::ColResize => {
709 winit::window::CursorIcon::ColResize
710 }
711 TheCursorIcon::RowResize => {
712 winit::window::CursorIcon::RowResize
713 }
714 };
715 ctx.window.set_cursor(cursor_icon);
716 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
717 ctx.ctx.reset_cursor_changed();
718 }
719 if ctx.ctx.cursor_visible_changed() {
720 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
721 ctx.ctx.reset_cursor_visible_changed();
722 }
723 }
724 }
725 WindowEvent::ModifiersChanged(modifiers) => {
726 let state = modifiers.state();
727 self.mods = state;
729
730 #[cfg(feature = "ui")]
731 if self.ui.modifier_changed(
732 state.shift_key(),
733 state.control_key(),
734 state.alt_key(),
735 state.super_key(),
736 &mut ctx.ctx,
737 ) {
738 ctx.window.request_redraw();
739 }
740 if self.app.modifier_changed(
741 state.shift_key(),
742 state.control_key(),
743 state.alt_key(),
744 state.super_key(),
745 ) {
746 ctx.window.request_redraw();
747 }
748 }
749 WindowEvent::CursorMoved { position, .. } => {
750 let (x, y) = translate_coord_to_local(
751 position.x as f32,
752 position.y as f32,
753 ctx.ctx.scale_factor,
754 );
755
756 self.last_cursor_pos = Some((x, y));
757
758 let mut redraw = false;
759 if self.left_mouse_down {
760 #[cfg(feature = "ui")]
761 if self.ui.touch_dragged(x, y, &mut ctx.ctx) {
762 redraw = true;
763 }
764
765 if self.app.touch_dragged(x, y, &mut ctx.ctx) {
766 redraw = true;
767 }
768 } else {
769 #[cfg(feature = "ui")]
770 if self.ui.hover(x, y, &mut ctx.ctx) {
771 redraw = true;
772 }
773
774 if self.app.hover(x, y, &mut ctx.ctx) {
775 redraw = true;
776 }
777 }
778
779 if ctx.ctx.cursor_changed() {
781 let cursor_icon = match ctx.ctx.cursor_icon() {
782 TheCursorIcon::Default => winit::window::CursorIcon::Default,
783 TheCursorIcon::Crosshair => winit::window::CursorIcon::Crosshair,
784 TheCursorIcon::Hand => winit::window::CursorIcon::Pointer,
785 TheCursorIcon::Arrow => winit::window::CursorIcon::Default,
786 TheCursorIcon::Text => winit::window::CursorIcon::Text,
787 TheCursorIcon::Wait => winit::window::CursorIcon::Wait,
788 TheCursorIcon::Help => winit::window::CursorIcon::Help,
789 TheCursorIcon::Progress => winit::window::CursorIcon::Progress,
790 TheCursorIcon::NotAllowed => winit::window::CursorIcon::NotAllowed,
791 TheCursorIcon::ContextMenu => {
792 winit::window::CursorIcon::ContextMenu
793 }
794 TheCursorIcon::Cell => winit::window::CursorIcon::Cell,
795 TheCursorIcon::VerticalText => {
796 winit::window::CursorIcon::VerticalText
797 }
798 TheCursorIcon::Alias => winit::window::CursorIcon::Alias,
799 TheCursorIcon::Copy => winit::window::CursorIcon::Copy,
800 TheCursorIcon::NoDrop => winit::window::CursorIcon::NoDrop,
801 TheCursorIcon::Grab => winit::window::CursorIcon::Grab,
802 TheCursorIcon::Grabbing => winit::window::CursorIcon::Grabbing,
803 TheCursorIcon::AllScroll => winit::window::CursorIcon::AllScroll,
804 TheCursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn,
805 TheCursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut,
806 TheCursorIcon::EResize => winit::window::CursorIcon::EResize,
807 TheCursorIcon::NResize => winit::window::CursorIcon::NResize,
808 TheCursorIcon::NEResize => winit::window::CursorIcon::NeResize,
809 TheCursorIcon::NWResize => winit::window::CursorIcon::NwResize,
810 TheCursorIcon::SResize => winit::window::CursorIcon::SResize,
811 TheCursorIcon::SEResize => winit::window::CursorIcon::SeResize,
812 TheCursorIcon::SWResize => winit::window::CursorIcon::SwResize,
813 TheCursorIcon::WResize => winit::window::CursorIcon::WResize,
814 TheCursorIcon::EWResize => winit::window::CursorIcon::EwResize,
815 TheCursorIcon::NSResize => winit::window::CursorIcon::NsResize,
816 TheCursorIcon::NESWResize => winit::window::CursorIcon::NeswResize,
817 TheCursorIcon::NWSEResize => winit::window::CursorIcon::NwseResize,
818 TheCursorIcon::ColResize => winit::window::CursorIcon::ColResize,
819 TheCursorIcon::RowResize => winit::window::CursorIcon::RowResize,
820 };
821 ctx.window.set_cursor(cursor_icon);
822 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
823 ctx.ctx.reset_cursor_changed();
824 }
825 if ctx.ctx.cursor_visible_changed() {
826 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
827 ctx.ctx.reset_cursor_visible_changed();
828 }
829
830 if redraw {
831 ctx.window.request_redraw();
832 }
833 }
834 WindowEvent::Touch(Touch {
835 phase, location, ..
836 }) => {
837 let (x, y) = translate_coord_to_local(
838 location.x as f32,
839 location.y as f32,
840 ctx.ctx.scale_factor,
841 );
842
843 match phase {
844 TouchPhase::Started => {
845 let mut redraw = false;
846 #[cfg(feature = "ui")]
847 {
848 if self.ui.touch_down(x as f32, y as f32, &mut ctx.ctx) {
849 redraw = true;
850 }
851 }
852 if self.app.touch_down(x as f32, y as f32, &mut ctx.ctx) {
853 redraw = true;
854 }
855
856 if redraw {
857 ctx.window.request_redraw();
858 }
859 }
860 TouchPhase::Moved => {
861 let mut redraw = false;
862 #[cfg(feature = "ui")]
863 {
864 if self.ui.touch_dragged(x as f32, y as f32, &mut ctx.ctx) {
865 redraw = true;
866 }
867 }
868 if self.app.touch_dragged(x as f32, y as f32, &mut ctx.ctx) {
869 redraw = true;
870 }
871 if redraw {
872 ctx.window.request_redraw();
873 }
874 }
875 TouchPhase::Ended | TouchPhase::Cancelled => {
876 let mut redraw = false;
877 #[cfg(feature = "ui")]
878 {
879 if self.ui.touch_up(x as f32, y as f32, &mut ctx.ctx) {
880 redraw = true;
881 }
882 }
883 if self.app.touch_up(x as f32, y as f32, &mut ctx.ctx) {
884 redraw = true;
885 }
886
887 if redraw {
888 ctx.window.request_redraw();
889 }
890 }
891 }
892 }
893 WindowEvent::MouseInput { state, button, .. } => {
894 if let Some((x, y)) = self.last_cursor_pos {
895 let mut redraw = false;
896 match (button, state) {
897 (MouseButton::Left, ElementState::Pressed) => {
898 self.left_mouse_down = true;
899
900 #[cfg(feature = "ui")]
901 if self.ui.touch_down(x, y, &mut ctx.ctx) {
902 redraw = true;
903 }
904
905 if self.app.touch_down(x, y, &mut ctx.ctx) {
906 redraw = true;
907 }
908 }
909 (MouseButton::Left, ElementState::Released) => {
910 self.left_mouse_down = false;
911
912 #[cfg(feature = "ui")]
913 if self.ui.touch_up(x, y, &mut ctx.ctx) {
914 redraw = true;
915 }
916
917 if self.app.touch_up(x, y, &mut ctx.ctx) {
918 redraw = true;
919 }
920 }
921 (MouseButton::Right, ElementState::Pressed) => {
922 #[cfg(feature = "ui")]
923 if self.ui.context(x, y, &mut ctx.ctx) {
924 redraw = true;
925 }
926
927 if self.app.touch_down(x, y, &mut ctx.ctx) {
928 redraw = true;
929 }
930 }
931 (MouseButton::Right, ElementState::Released) => {
932 #[cfg(feature = "ui")]
933 if self.ui.touch_up(x, y, &mut ctx.ctx) {
934 redraw = true;
935 }
936
937 if self.app.touch_up(x, y, &mut ctx.ctx) {
938 redraw = true;
939 }
940 }
941 _ => {}
942 }
943
944 if ctx.ctx.cursor_changed() {
946 let cursor_icon = match ctx.ctx.cursor_icon() {
947 TheCursorIcon::Default => winit::window::CursorIcon::Default,
948 TheCursorIcon::Crosshair => {
949 winit::window::CursorIcon::Crosshair
950 }
951 TheCursorIcon::Hand => winit::window::CursorIcon::Pointer,
952 TheCursorIcon::Arrow => winit::window::CursorIcon::Default,
953 TheCursorIcon::Text => winit::window::CursorIcon::Text,
954 TheCursorIcon::Wait => winit::window::CursorIcon::Wait,
955 TheCursorIcon::Help => winit::window::CursorIcon::Help,
956 TheCursorIcon::Progress => winit::window::CursorIcon::Progress,
957 TheCursorIcon::NotAllowed => {
958 winit::window::CursorIcon::NotAllowed
959 }
960 TheCursorIcon::ContextMenu => {
961 winit::window::CursorIcon::ContextMenu
962 }
963 TheCursorIcon::Cell => winit::window::CursorIcon::Cell,
964 TheCursorIcon::VerticalText => {
965 winit::window::CursorIcon::VerticalText
966 }
967 TheCursorIcon::Alias => winit::window::CursorIcon::Alias,
968 TheCursorIcon::Copy => winit::window::CursorIcon::Copy,
969 TheCursorIcon::NoDrop => winit::window::CursorIcon::NoDrop,
970 TheCursorIcon::Grab => winit::window::CursorIcon::Grab,
971 TheCursorIcon::Grabbing => winit::window::CursorIcon::Grabbing,
972 TheCursorIcon::AllScroll => {
973 winit::window::CursorIcon::AllScroll
974 }
975 TheCursorIcon::ZoomIn => winit::window::CursorIcon::ZoomIn,
976 TheCursorIcon::ZoomOut => winit::window::CursorIcon::ZoomOut,
977 TheCursorIcon::EResize => winit::window::CursorIcon::EResize,
978 TheCursorIcon::NResize => winit::window::CursorIcon::NResize,
979 TheCursorIcon::NEResize => winit::window::CursorIcon::NeResize,
980 TheCursorIcon::NWResize => winit::window::CursorIcon::NwResize,
981 TheCursorIcon::SResize => winit::window::CursorIcon::SResize,
982 TheCursorIcon::SEResize => winit::window::CursorIcon::SeResize,
983 TheCursorIcon::SWResize => winit::window::CursorIcon::SwResize,
984 TheCursorIcon::WResize => winit::window::CursorIcon::WResize,
985 TheCursorIcon::EWResize => winit::window::CursorIcon::EwResize,
986 TheCursorIcon::NSResize => winit::window::CursorIcon::NsResize,
987 TheCursorIcon::NESWResize => {
988 winit::window::CursorIcon::NeswResize
989 }
990 TheCursorIcon::NWSEResize => {
991 winit::window::CursorIcon::NwseResize
992 }
993 TheCursorIcon::ColResize => {
994 winit::window::CursorIcon::ColResize
995 }
996 TheCursorIcon::RowResize => {
997 winit::window::CursorIcon::RowResize
998 }
999 };
1000 ctx.window.set_cursor(cursor_icon);
1001 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
1002 ctx.ctx.reset_cursor_changed();
1003 }
1004 if ctx.ctx.cursor_visible_changed() {
1005 ctx.window.set_cursor_visible(ctx.ctx.cursor_visible());
1006 ctx.ctx.reset_cursor_visible_changed();
1007 }
1008
1009 if redraw {
1010 ctx.window.request_redraw();
1011 }
1012 }
1013 }
1014 WindowEvent::MouseWheel { delta, .. } => {
1015 let (x, y) = match delta {
1016 MouseScrollDelta::LineDelta(x, y) => {
1017 const LINE_HEIGHT_PX: f32 = 20.0;
1018 (x as f32 * LINE_HEIGHT_PX, y as f32 * LINE_HEIGHT_PX)
1019 }
1020 MouseScrollDelta::PixelDelta(delta) => (delta.x as f32, delta.y as f32),
1021 };
1022
1023 let mut redraw = false;
1024 #[cfg(feature = "ui")]
1025 if self.ui.mouse_wheel((x as i32, y as i32), &mut ctx.ctx) {
1026 redraw = true;
1027 }
1028
1029 if self.app.mouse_wheel((x as isize, y as isize), &mut ctx.ctx) {
1030 redraw = true;
1031 }
1032
1033 if redraw {
1034 ctx.window.request_redraw();
1035 }
1036 }
1037 WindowEvent::DroppedFile(path) => {
1038 self.app.dropped_file(path.to_string_lossy().into_owned());
1039 ctx.window.request_redraw();
1040 }
1041 _ => {}
1042 }
1043 }
1044 }
1045 }
1046
1047 fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, _: DeviceEvent) {}
1048
1049 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1050 let now = Instant::now();
1051 let fps = self.app.target_fps().max(1.0);
1052 let target_frame_time = Duration::from_secs_f64(1.0 / fps);
1053 if target_frame_time != self.target_frame_time {
1054 self.target_frame_time = target_frame_time;
1055 self.next_frame_time = now + self.target_frame_time;
1056 }
1057
1058 let timer_due = now >= self.next_frame_time;
1059 #[cfg(target_arch = "wasm32")]
1060 let should_redraw = true;
1061 #[cfg(not(target_arch = "wasm32"))]
1062 let should_redraw = timer_due;
1063
1064 #[cfg(target_arch = "wasm32")]
1065 {
1066 if let Some(ctx) = &self.ctx {
1067 ctx.window.request_redraw();
1068 }
1069 }
1070
1071 #[cfg(not(target_arch = "wasm32"))]
1072 {
1073 if should_redraw {
1074 if let Some(ctx) = &self.ctx {
1075 ctx.window.request_redraw();
1076 }
1077 }
1078 }
1079
1080 if timer_due {
1081 while self.next_frame_time <= now {
1082 self.next_frame_time += self.target_frame_time;
1083 }
1084 }
1085
1086 #[cfg(target_arch = "wasm32")]
1087 {
1088 event_loop.set_control_flow(ControlFlow::Poll);
1089 }
1090
1091 #[cfg(not(target_arch = "wasm32"))]
1092 {
1093 let now_after_schedule = Instant::now();
1094 let wake_ahead = Duration::from_millis(2);
1095 if self.next_frame_time > now_after_schedule + wake_ahead {
1096 event_loop
1097 .set_control_flow(ControlFlow::WaitUntil(self.next_frame_time - wake_ahead));
1098 } else {
1099 event_loop.set_control_flow(ControlFlow::Poll);
1101 }
1102 }
1103
1104 if !should_redraw {
1105 return;
1106 }
1107
1108 let Some(window) = self.ctx.as_ref().map(|ctx| ctx.window.clone()) else {
1109 return;
1110 };
1111
1112 if self.process_updates() {
1113 window.request_redraw();
1114 }
1115 }
1116}
1117
1118pub fn run_winit_app(args: Option<Vec<String>>, app: Box<dyn TheTrait>) {
1119 #[cfg(target_arch = "wasm32")]
1120 console_error_panic_hook::set_once();
1121
1122 let mut winit_app = TheWinitApp::new(args, app);
1123
1124 let event_loop = EventLoop::new().unwrap();
1125 event_loop.run_app(&mut winit_app).unwrap();
1126}