From bedf788386c708d182f6946812af8f241743af20 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Sat, 5 Oct 2024 22:40:03 +0200 Subject: [PATCH] desktop: Switch to ApplicationHandler model of winit --- desktop/src/app.rs | 845 ++++++++++++++++++++++---------------------- desktop/src/main.rs | 8 +- 2 files changed, 428 insertions(+), 425 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 634def4a5450f..7bc3ebd209d35 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -15,29 +15,38 @@ use std::rc::Rc; use std::sync::Arc; use std::time::{Duration, Instant}; use url::Url; +use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Size}; use winit::event::{ElementState, KeyEvent, Modifiers, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy}; use winit::keyboard::{Key, NamedKey}; -use winit::window::{Fullscreen, Icon, Window, WindowAttributes}; +use winit::window::{Fullscreen, Icon, Window, WindowAttributes, WindowId}; pub struct App { preferences: GlobalPreferences, window: Arc, - event_loop: Option>, gui: Rc>, player: PlayerController, min_window_size: LogicalSize, max_window_size: PhysicalSize, - initial_movie_url: Option, no_gui: bool, preferred_width: Option, preferred_height: Option, start_fullscreen: bool, + loaded: LoadingState, + gilrs: Option, + event_loop_proxy: EventLoopProxy, + minimized: bool, + mouse_pos: PhysicalPosition, + modifiers: Modifiers, + time: Instant, + next_frame_time: Option, } impl App { - pub async fn new(preferences: GlobalPreferences) -> Result { + pub async fn new( + preferences: GlobalPreferences, + ) -> Result<(Self, EventLoop), Error> { let movie_url = preferences.cli.movie_url.clone(); let icon_bytes = include_bytes!("../assets/favicon-32.rgba"); let icon = @@ -97,492 +106,482 @@ impl App { gui.show_open_dialog(); } - Ok(Self { - preferences, - window, - event_loop: Some(event_loop), - gui: Rc::new(RefCell::new(gui)), - player, - min_window_size, - max_window_size, - initial_movie_url: movie_url, - no_gui, - preferred_width, - preferred_height, - start_fullscreen, - }) - } - - pub fn run(mut self) -> Result<(), Error> { - enum LoadingState { - Loading, - WaitingForResize, - Loaded, - } let mut loaded = LoadingState::Loading; - let mut mouse_pos = PhysicalPosition::new(0.0, 0.0); - let mut time = Instant::now(); - let mut next_frame_time = None; - let mut minimized = false; - let mut modifiers = Modifiers::default(); - if self.initial_movie_url.is_none() { + if movie_url.is_none() { // No SWF provided on command line; show window with dummy movie immediately. - self.window.set_visible(true); + window.set_visible(true); loaded = LoadingState::Loaded; } - let mut gilrs = Gilrs::new() + let gilrs = Gilrs::new() .inspect_err(|err| { tracing::warn!("Gamepad support could not be initialized: {err}"); }) .ok(); - - // Poll UI events. - let event_loop = self.event_loop.take().expect("App already running"); let event_loop_proxy = event_loop.create_proxy(); - // TODO: Migrate to `EventLoop::run_app` and `impl ApplicationHandler for App`, - // see: https://github.com/rust-windowing/winit/releases/tag/v0.30.0 - #[allow(deprecated)] - event_loop.run(move |event, elwt| { - let mut check_redraw = false; - match event { - winit::event::Event::LoopExiting => { - if let Some(mut player) = self.player.get() { - player.flush_shared_objects(); - } - crate::shutdown(); - return; - } - // Core loop - // [NA] This used to be called `MainEventsCleared`, but I think the behaviour is different now. - // We should look at changing our tick to happen somewhere else if we see any behavioural problems. - winit::event::Event::AboutToWait if matches!(loaded, LoadingState::Loaded) => { - let new_time = Instant::now(); - let dt = new_time.duration_since(time).as_micros(); - if dt > 0 { - time = new_time; - if let Some(mut player) = self.player.get() { - player.tick(dt as f64 / 1000.0); - next_frame_time = Some(new_time + player.time_til_next_frame()); - } else { - next_frame_time = None; - } - check_redraw = true; - } - } + Ok(( + Self { + preferences, + window, + gui: Rc::new(RefCell::new(gui)), + player, + min_window_size, + max_window_size, + no_gui, + preferred_width, + preferred_height, + start_fullscreen, + loaded, + gilrs, + event_loop_proxy, + minimized: false, + mouse_pos: PhysicalPosition::new(0.0, 0.0), + modifiers: Modifiers::default(), + time: Instant::now(), + next_frame_time: None, + }, + event_loop, + )) + } - // Render - winit::event::Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - // Don't render when minimized to avoid potential swap chain errors in `wgpu`. - if !minimized { - if let Some(mut player) = self.player.get() { - // Even if the movie is paused, user interaction with debug tools can change the render output - player.render(); - self.gui.borrow_mut().render(Some(player)); - } else { - self.gui.borrow_mut().render(None); - } - plot_stats_in_tracy(&self.gui.borrow().descriptors().wgpu_instance); - } - } + fn check_redraw(&self) { + let player = self.player.get(); + let gui = self.gui.borrow_mut(); + if player.map(|p| p.needs_render()).unwrap_or_default() || gui.needs_render() { + self.window.request_redraw(); + } + } +} - winit::event::Event::WindowEvent { event, .. } => { - if self.gui.borrow_mut().handle_event(&event) { - // Event consumed by GUI. - return; - } - let height_offset = if self.window.fullscreen().is_some() || self.no_gui { - 0.0 - } else { - MENU_HEIGHT as f64 * self.window.scale_factor() - }; - match event { - WindowEvent::CloseRequested => { - elwt.exit(); - return; - } - WindowEvent::Resized(size) => { - // TODO: Change this when winit adds a `Window::minimized` or `WindowEvent::Minimize`. - minimized = size.width == 0 && size.height == 0; - - if let Some(mut player) = self.player.get() { - let viewport_scale_factor = self.window.scale_factor(); - player.set_viewport_dimensions(ViewportDimensions { - width: size.width, - height: size.height.saturating_sub(height_offset as u32), - scale_factor: viewport_scale_factor, - }); - } - self.window.request_redraw(); - if matches!(loaded, LoadingState::WaitingForResize) { - loaded = LoadingState::Loaded; - } - } - WindowEvent::CursorMoved { position, .. } => { - if self.gui.borrow_mut().is_context_menu_visible() { - return; - } +impl ApplicationHandler for App { + fn resumed(&mut self, _event_loop: &ActiveEventLoop) {} - mouse_pos = position; - let event = PlayerEvent::MouseMove { - x: position.x, - y: position.y - height_offset, - }; - self.player.handle_event(event); - check_redraw = true; - } - WindowEvent::DroppedFile(file) => { - if let Ok(url) = parse_url(&file) { - self.gui.borrow_mut().create_movie( - &mut self.player, - LaunchOptions::from(&self.preferences), - url, - ); - } - } - WindowEvent::Focused(true) => { - self.player.handle_event(PlayerEvent::FocusGained); - } - WindowEvent::Focused(false) => { - self.player.handle_event(PlayerEvent::FocusLost); - } - WindowEvent::MouseInput { button, state, .. } => { - if self.gui.borrow_mut().is_context_menu_visible() { - return; - } + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: RuffleEvent) { + match event { + RuffleEvent::TaskPoll => self.player.poll(), + RuffleEvent::OnMetadata(swf_header) => { + let height_offset = if self.window.fullscreen().is_some() || self.no_gui { + 0.0 + } else { + MENU_HEIGHT as f64 + }; - use ruffle_core::events::MouseButton as RuffleMouseButton; - use winit::event::MouseButton; - let x = mouse_pos.x; - let y = mouse_pos.y - height_offset; - let button = match button { - MouseButton::Left => RuffleMouseButton::Left, - MouseButton::Right => RuffleMouseButton::Right, - MouseButton::Middle => RuffleMouseButton::Middle, - _ => RuffleMouseButton::Unknown, - }; - let event = match state { - // TODO We should get information about click index from the OS, - // but winit does not support that yet. - ElementState::Pressed => PlayerEvent::MouseDown { - x, - y, - button, - index: None, - }, - ElementState::Released => PlayerEvent::MouseUp { x, y, button }, - }; - let handled = self.player.handle_event(event); - if !handled - && state == ElementState::Pressed - && button == RuffleMouseButton::Right - { - // Show context menu. - if let Some(mut player) = self.player.get() { - let context_menu = player.prepare_context_menu(); - - // MouseUp event will be ignored when the context menu is shown, - // but it has to be dispatched when the menu closes. - let close_event = PlayerEvent::MouseUp { - x, - y, - button: RuffleMouseButton::Right, - }; - self.gui - .borrow_mut() - .show_context_menu(context_menu, close_event); - } - } - check_redraw = true; - } - WindowEvent::MouseWheel { delta, .. } => { - if self.gui.borrow_mut().is_context_menu_visible() { - return; - } + // To prevent issues like waiting on resize indefinitely (#11364) or desyncing the window state on Windows, + // do not resize while window is maximized. + let should_resize = !self.window.is_maximized(); - use ruffle_core::events::MouseWheelDelta; - use winit::event::MouseScrollDelta; - let delta = match delta { - MouseScrollDelta::LineDelta(_, dy) => { - MouseWheelDelta::Lines(dy.into()) - } - MouseScrollDelta::PixelDelta(pos) => MouseWheelDelta::Pixels(pos.y), - }; - let event = PlayerEvent::MouseWheel { delta }; - self.player.handle_event(event); - check_redraw = true; - } - WindowEvent::CursorEntered { .. } => { - if let Some(mut player) = self.player.get() { - player.set_mouse_in_stage(true); - if player.needs_render() { - self.window.request_redraw(); - } - } + let viewport_size = if should_resize { + let movie_width = swf_header.stage_size().width().to_pixels(); + let movie_height = swf_header.stage_size().height().to_pixels(); + + let window_size: Size = match (self.preferred_width, self.preferred_height) { + (None, None) => { + LogicalSize::new(movie_width, movie_height + height_offset).into() } - WindowEvent::CursorLeft { .. } => { - if let Some(mut player) = self.player.get() { - player.set_mouse_in_stage(false); - } - self.player.handle_event(PlayerEvent::MouseLeave); - check_redraw = true; + (Some(width), None) => { + let scale = width / movie_width; + let height = movie_height * scale; + PhysicalSize::new( + width.max(1.0), + height.max(1.0) + height_offset * self.window.scale_factor(), + ) + .into() } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; + (None, Some(height)) => { + let scale = height / movie_height; + let width = movie_width * scale; + PhysicalSize::new( + width.max(1.0), + height.max(1.0) + height_offset * self.window.scale_factor(), + ) + .into() } - WindowEvent::KeyboardInput { event, .. } => { - if self.gui.borrow_mut().is_context_menu_visible() { - return; - } + (Some(width), Some(height)) => PhysicalSize::new( + width.max(1.0), + height.max(1.0) + height_offset * self.window.scale_factor(), + ) + .into(), + }; - // Handle escaping from fullscreen. - if let KeyEvent { - state: ElementState::Pressed, - logical_key: Key::Named(NamedKey::Escape), - .. - } = event - { - let _ = event_loop_proxy.send_event(RuffleEvent::ExitFullScreen); - } + let window_size = Size::clamp( + window_size, + self.min_window_size.into(), + self.max_window_size.into(), + self.window.scale_factor(), + ); - let key_code = winit_to_ruffle_key_code(&event); - // [NA] TODO: This event used to give a single char. `last()` is functionally the same, - // but we may want to be better at this in the future. - let key_char = event.text.clone().and_then(|text| text.chars().last()); - - match (key_code, &event.state) { - (Some(key_code), ElementState::Pressed) => { - self.player - .handle_event(PlayerEvent::KeyDown { key_code, key_char }); - if let Some(control_code) = - winit_to_ruffle_text_control(&event, &modifiers) - { - self.player.handle_event(PlayerEvent::TextControl { - code: control_code, - }); - } else if let Some(text) = event.text { - for codepoint in text.chars() { - self.player - .handle_event(PlayerEvent::TextInput { codepoint }); - } - } - } - (Some(key_code), ElementState::Released) => { - self.player - .handle_event(PlayerEvent::KeyUp { key_code, key_char }); - } - _ => {} - }; - check_redraw = true; + let viewport_size = self.window.inner_size(); + let mut window_resize_denied = false; + + if let Some(new_viewport_size) = self.window.request_inner_size(window_size) { + if new_viewport_size != viewport_size { + self.gui.borrow_mut().resize(new_viewport_size); + } else { + tracing::warn!("Unable to resize window"); + window_resize_denied = true; } - _ => (), } - } - winit::event::Event::UserEvent(RuffleEvent::TaskPoll) => self.player.poll(), - winit::event::Event::UserEvent(RuffleEvent::OnMetadata(swf_header)) => { - let height_offset = if self.window.fullscreen().is_some() || self.no_gui { - 0.0 - } else { - MENU_HEIGHT as f64 - }; - // To prevent issues like waiting on resize indefinitely (#11364) or desyncing the window state on Windows, - // do not resize while window is maximized. - let should_resize = !self.window.is_maximized(); + let viewport_size = self.window.inner_size(); - let viewport_size = if should_resize { - let movie_width = swf_header.stage_size().width().to_pixels(); - let movie_height = swf_header.stage_size().height().to_pixels(); - - let window_size: Size = match (self.preferred_width, self.preferred_height) - { - (None, None) => { - LogicalSize::new(movie_width, movie_height + height_offset).into() - } - (Some(width), None) => { - let scale = width / movie_width; - let height = movie_height * scale; - PhysicalSize::new( - width.max(1.0), - height.max(1.0) + height_offset * self.window.scale_factor(), - ) - .into() - } - (None, Some(height)) => { - let scale = height / movie_height; - let width = movie_width * scale; - PhysicalSize::new( - width.max(1.0), - height.max(1.0) + height_offset * self.window.scale_factor(), - ) - .into() - } - (Some(width), Some(height)) => PhysicalSize::new( - width.max(1.0), - height.max(1.0) + height_offset * self.window.scale_factor(), - ) - .into(), - }; + // On X11 (and possibly other platforms), the window size is not updated immediately. + // On a successful resize request, wait for the window to be resized to the requested size + // before we start running the SWF (which can observe the viewport size in "noScale" mode) + if !window_resize_denied && window_size != viewport_size.into() { + self.loaded = LoadingState::WaitingForResize; + } else { + self.loaded = LoadingState::Loaded; + } - let window_size = Size::clamp( - window_size, - self.min_window_size.into(), - self.max_window_size.into(), - self.window.scale_factor(), - ); + viewport_size + } else { + self.window.inner_size() + }; - let viewport_size = self.window.inner_size(); - let mut window_resize_denied = false; + self.window.set_fullscreen(if self.start_fullscreen { + Some(Fullscreen::Borderless(None)) + } else { + None + }); + self.window.set_visible(true); + + let viewport_scale_factor = self.window.scale_factor(); + if let Some(mut player) = self.player.get() { + player.set_viewport_dimensions(ViewportDimensions { + width: viewport_size.width, + height: viewport_size.height - height_offset as u32, + scale_factor: viewport_scale_factor, + }); + } + } - if let Some(new_viewport_size) = self.window.request_inner_size(window_size) - { - if new_viewport_size != viewport_size { - self.gui.borrow_mut().resize(new_viewport_size); - } else { - tracing::warn!("Unable to resize window"); - window_resize_denied = true; - } - } + RuffleEvent::ContextMenuItemClicked(index) => { + if let Some(mut player) = self.player.get() { + player.run_context_menu_callback(index); + } + } - let viewport_size = self.window.inner_size(); + RuffleEvent::BrowseAndOpen(options) => { + let event_loop = self.event_loop_proxy.clone(); + let picker = self.gui.borrow().file_picker(); + tokio::spawn(async move { + if let Some(url) = picker + .pick_ruffle_file(None) + .await + .and_then(|p| Url::from_file_path(p).ok()) + { + let _ = event_loop.send_event(RuffleEvent::Open(url, options)); + } + }); + } - // On X11 (and possibly other platforms), the window size is not updated immediately. - // On a successful resize request, wait for the window to be resized to the requested size - // before we start running the SWF (which can observe the viewport size in "noScale" mode) - if !window_resize_denied && window_size != viewport_size.into() { - loaded = LoadingState::WaitingForResize; - } else { - loaded = LoadingState::Loaded; - } + RuffleEvent::Open(url, options) => { + self.gui + .borrow_mut() + .create_movie(&mut self.player, *options, url); + } - viewport_size - } else { - self.window.inner_size() - }; + RuffleEvent::OpenDialog(descriptor) => { + self.gui.borrow_mut().open_dialog(descriptor); + } - self.window.set_fullscreen(if self.start_fullscreen { - Some(Fullscreen::Borderless(None)) - } else { - None - }); - self.window.set_visible(true); + RuffleEvent::CloseFile => { + self.window.set_title("Ruffle"); // Reset title since file has been closed. + self.player.destroy(); + } - let viewport_scale_factor = self.window.scale_factor(); - if let Some(mut player) = self.player.get() { - player.set_viewport_dimensions(ViewportDimensions { - width: viewport_size.width, - height: viewport_size.height - height_offset as u32, - scale_factor: viewport_scale_factor, - }); + RuffleEvent::EnterFullScreen => { + if let Some(mut player) = self.player.get() { + if player.is_playing() { + player.set_fullscreen(true); } } + } - winit::event::Event::UserEvent(RuffleEvent::ContextMenuItemClicked(index)) => { - if let Some(mut player) = self.player.get() { - player.run_context_menu_callback(index); + RuffleEvent::ExitFullScreen => { + if let Some(mut player) = self.player.get() { + if player.is_playing() { + player.set_fullscreen(false); } } + } - winit::event::Event::UserEvent(RuffleEvent::BrowseAndOpen(options)) => { - let event_loop = event_loop_proxy.clone(); - let picker = self.gui.borrow().file_picker(); - tokio::spawn(async move { - if let Some(url) = picker - .pick_ruffle_file(None) - .await - .and_then(|p| Url::from_file_path(p).ok()) - { - let _ = event_loop.send_event(RuffleEvent::Open(url, options)); - } - }); - } + RuffleEvent::ExitRequested => { + event_loop.exit(); + } + } + } - winit::event::Event::UserEvent(RuffleEvent::Open(url, options)) => { - self.gui - .borrow_mut() - .create_movie(&mut self.player, *options, url); + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + if matches!(event, WindowEvent::RedrawRequested) { + // Don't render when minimized to avoid potential swap chain errors in `wgpu`. + if !self.minimized { + if let Some(mut player) = self.player.get() { + // Even if the movie is paused, user interaction with debug tools can change the render output + player.render(); + self.gui.borrow_mut().render(Some(player)); + } else { + self.gui.borrow_mut().render(None); } + plot_stats_in_tracy(&self.gui.borrow().descriptors().wgpu_instance); + } + + // Important that we return here, or we'll get a feedback loop with egui + // (winit says redraw, egui hears redraw and says redraw, we hear redraw and tell winit to redraw...) + return; + } + + if self.gui.borrow_mut().handle_event(&event) { + // Event consumed by GUI. + return; + } + let height_offset = if self.window.fullscreen().is_some() || self.no_gui { + 0.0 + } else { + MENU_HEIGHT as f64 * self.window.scale_factor() + }; + match event { + WindowEvent::CloseRequested => { + event_loop.exit(); + } + WindowEvent::Resized(size) => { + // TODO: Change this when winit adds a `Window::minimized` or `WindowEvent::Minimize`. + self.minimized = size.width == 0 && size.height == 0; - winit::event::Event::UserEvent(RuffleEvent::OpenDialog(descriptor)) => { - self.gui.borrow_mut().open_dialog(descriptor); + if let Some(mut player) = self.player.get() { + let viewport_scale_factor = self.window.scale_factor(); + player.set_viewport_dimensions(ViewportDimensions { + width: size.width, + height: size.height.saturating_sub(height_offset as u32), + scale_factor: viewport_scale_factor, + }); + } + self.window.request_redraw(); + if matches!(self.loaded, LoadingState::WaitingForResize) { + self.loaded = LoadingState::Loaded; + } + } + WindowEvent::CursorMoved { position, .. } => { + if self.gui.borrow_mut().is_context_menu_visible() { + return; } - winit::event::Event::UserEvent(RuffleEvent::CloseFile) => { - self.window.set_title("Ruffle"); // Reset title since file has been closed. - self.player.destroy(); + self.mouse_pos = position; + let event = PlayerEvent::MouseMove { + x: position.x, + y: position.y - height_offset, + }; + self.player.handle_event(event); + self.check_redraw(); + } + WindowEvent::DroppedFile(file) => { + if let Ok(url) = parse_url(&file) { + self.gui.borrow_mut().create_movie( + &mut self.player, + LaunchOptions::from(&self.preferences), + url, + ); + } + } + WindowEvent::Focused(true) => { + self.player.handle_event(PlayerEvent::FocusGained); + } + WindowEvent::Focused(false) => { + self.player.handle_event(PlayerEvent::FocusLost); + } + WindowEvent::MouseInput { button, state, .. } => { + if self.gui.borrow_mut().is_context_menu_visible() { + return; } - winit::event::Event::UserEvent(RuffleEvent::EnterFullScreen) => { + use ruffle_core::events::MouseButton as RuffleMouseButton; + use winit::event::MouseButton; + let x = self.mouse_pos.x; + let y = self.mouse_pos.y - height_offset; + let button = match button { + MouseButton::Left => RuffleMouseButton::Left, + MouseButton::Right => RuffleMouseButton::Right, + MouseButton::Middle => RuffleMouseButton::Middle, + _ => RuffleMouseButton::Unknown, + }; + let event = match state { + // TODO We should get information about click index from the OS, + // but winit does not support that yet. + ElementState::Pressed => PlayerEvent::MouseDown { + x, + y, + button, + index: None, + }, + ElementState::Released => PlayerEvent::MouseUp { x, y, button }, + }; + let handled = self.player.handle_event(event); + if !handled && state == ElementState::Pressed && button == RuffleMouseButton::Right + { + // Show context menu. if let Some(mut player) = self.player.get() { - if player.is_playing() { - player.set_fullscreen(true); - } + let context_menu = player.prepare_context_menu(); + + // MouseUp event will be ignored when the context menu is shown, + // but it has to be dispatched when the menu closes. + let close_event = PlayerEvent::MouseUp { + x, + y, + button: RuffleMouseButton::Right, + }; + self.gui + .borrow_mut() + .show_context_menu(context_menu, close_event); } } + self.check_redraw(); + } + WindowEvent::MouseWheel { delta, .. } => { + if self.gui.borrow_mut().is_context_menu_visible() { + return; + } - winit::event::Event::UserEvent(RuffleEvent::ExitFullScreen) => { - if let Some(mut player) = self.player.get() { - if player.is_playing() { - player.set_fullscreen(false); - } + use ruffle_core::events::MouseWheelDelta; + use winit::event::MouseScrollDelta; + let delta = match delta { + MouseScrollDelta::LineDelta(_, dy) => MouseWheelDelta::Lines(dy.into()), + MouseScrollDelta::PixelDelta(pos) => MouseWheelDelta::Pixels(pos.y), + }; + let event = PlayerEvent::MouseWheel { delta }; + self.player.handle_event(event); + self.check_redraw(); + } + WindowEvent::CursorEntered { .. } => { + if let Some(mut player) = self.player.get() { + player.set_mouse_in_stage(true); + if player.needs_render() { + self.window.request_redraw(); } } - - winit::event::Event::UserEvent(RuffleEvent::ExitRequested) => { - elwt.exit(); + } + WindowEvent::CursorLeft { .. } => { + if let Some(mut player) = self.player.get() { + player.set_mouse_in_stage(false); + } + self.player.handle_event(PlayerEvent::MouseLeave); + self.check_redraw(); + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = new_modifiers; + } + WindowEvent::KeyboardInput { event, .. } => { + if self.gui.borrow_mut().is_context_menu_visible() { return; } - _ => (), - } + // Handle escaping from fullscreen. + if let KeyEvent { + state: ElementState::Pressed, + logical_key: Key::Named(NamedKey::Escape), + .. + } = event + { + let _ = self + .event_loop_proxy + .send_event(RuffleEvent::ExitFullScreen); + } - if let Some(Event { event, .. }) = gilrs.as_mut().and_then(|gilrs| gilrs.next_event()) { - match event { - EventType::ButtonPressed(button, _) => { - if let Some(button) = gilrs_button_to_gamepad_button(button) { + let key_code = winit_to_ruffle_key_code(&event); + // [NA] TODO: This event used to give a single char. `last()` is functionally the same, + // but we may want to be better at this in the future. + let key_char = event.text.clone().and_then(|text| text.chars().last()); + + match (key_code, &event.state) { + (Some(key_code), ElementState::Pressed) => { + self.player + .handle_event(PlayerEvent::KeyDown { key_code, key_char }); + if let Some(control_code) = + winit_to_ruffle_text_control(&event, &self.modifiers) + { self.player - .handle_event(PlayerEvent::GamepadButtonDown { button }); - check_redraw = true; + .handle_event(PlayerEvent::TextControl { code: control_code }); + } else if let Some(text) = event.text { + for codepoint in text.chars() { + self.player + .handle_event(PlayerEvent::TextInput { codepoint }); + } } } - EventType::ButtonReleased(button, _) => { - if let Some(button) = gilrs_button_to_gamepad_button(button) { - self.player - .handle_event(PlayerEvent::GamepadButtonUp { button }); - check_redraw = true; - } + (Some(key_code), ElementState::Released) => { + self.player + .handle_event(PlayerEvent::KeyUp { key_code, key_char }); } _ => {} - } + }; + self.check_redraw(); } + _ => (), + } + } - // Check for a redraw request. - if check_redraw { - let player = self.player.get(); - let gui = self.gui.borrow_mut(); - if player.map(|p| p.needs_render()).unwrap_or_default() || gui.needs_render() { - self.window.request_redraw(); + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + if let Some(Event { event, .. }) = self.gilrs.as_mut().and_then(|gilrs| gilrs.next_event()) + { + match event { + EventType::ButtonPressed(button, _) => { + if let Some(button) = gilrs_button_to_gamepad_button(button) { + self.player + .handle_event(PlayerEvent::GamepadButtonDown { button }); + self.check_redraw(); + } } + EventType::ButtonReleased(button, _) => { + if let Some(button) = gilrs_button_to_gamepad_button(button) { + self.player + .handle_event(PlayerEvent::GamepadButtonUp { button }); + self.check_redraw(); + } + } + _ => {} } + } - // After polling events, sleep the event loop until the next event or the next frame. - elwt.set_control_flow(if matches!(loaded, LoadingState::Loaded) { - if let Some(next_frame_time) = next_frame_time { - ControlFlow::WaitUntil(next_frame_time) + // Core loop + // [NA] This used to be called `MainEventsCleared`, but I think the behaviour is different now. + // We should look at changing our tick to happen somewhere else if we see any behavioural problems. + if matches!(self.loaded, LoadingState::Loaded) { + let new_time = Instant::now(); + let dt = new_time.duration_since(self.time).as_micros(); + if dt > 0 { + self.time = new_time; + if let Some(mut player) = self.player.get() { + player.tick(dt as f64 / 1000.0); + self.next_frame_time = Some(new_time + player.time_til_next_frame()); } else { - // prevent 100% cpu use - // TODO: use set_request_repaint_callback to correctly get egui repaint requests. - ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10)) + self.next_frame_time = None; } - } else { - ControlFlow::Wait - }); - })?; - Ok(()) + self.check_redraw(); + // After polling events, sleep the event loop until the next event or the next frame. + event_loop.set_control_flow(if matches!(self.loaded, LoadingState::Loaded) { + if let Some(next_frame_time) = self.next_frame_time { + ControlFlow::WaitUntil(next_frame_time) + } else { + // prevent 100% cpu use + // TODO: use set_request_repaint_callback to correctly get egui repaint requests. + ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10)) + } + } else { + ControlFlow::Wait + }); + } + } } } + +enum LoadingState { + Loading, + WaitingForResize, + Loaded, +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 027404a155796..cc26e1b4acb10 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -20,7 +20,7 @@ mod tracy; mod util; use crate::preferences::GlobalPreferences; -use anyhow::Error; +use anyhow::{Context, Error}; use app::App; use clap::Parser; use cli::Opt; @@ -188,7 +188,11 @@ async fn main() -> Result<(), Error> { subscriber.init(); - let result = App::new(preferences).await.and_then(|app| app.run()); + let result = App::new(preferences) + .await + .and_then(|(mut app, event_loop)| { + event_loop.run_app(&mut app).context("Event loop failure") + }); #[cfg(windows)] if let Err(error) = &result {