diff --git a/src/platform_impl/ios/app_delegate.rs b/src/platform_impl/ios/app_delegate.rs index 0be14d1b66..2328c58c50 100644 --- a/src/platform_impl/ios/app_delegate.rs +++ b/src/platform_impl/ios/app_delegate.rs @@ -1,11 +1,9 @@ use objc2::{declare_class, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol}; -use objc2_ui_kit::{UIApplication, UIWindow}; +use objc2_foundation::{MainThreadMarker, NSObject}; +use objc2_ui_kit::UIApplication; -use super::app_state::{self, EventWrapper}; -use super::window::WinitUIWindow; -use crate::event::{Event, WindowEvent}; -use crate::window::WindowId as RootWindowId; +use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper}; +use crate::event::Event; declare_class!( pub struct AppDelegate; @@ -40,35 +38,17 @@ declare_class!( #[method(applicationWillEnterForeground:)] fn will_enter_foreground(&self, application: &UIApplication) { - self.send_occluded_event_for_all_windows(application, false); + send_occluded_event_for_all_windows(application, false); } #[method(applicationDidEnterBackground:)] fn did_enter_background(&self, application: &UIApplication) { - self.send_occluded_event_for_all_windows(application, true); + send_occluded_event_for_all_windows(application, true); } #[method(applicationWillTerminate:)] fn will_terminate(&self, application: &UIApplication) { - let mut events = Vec::new(); - #[allow(deprecated)] - for window in application.windows().iter() { - if window.is_kind_of::() { - // SAFETY: We just checked that the window is a `winit` window - let window = unsafe { - let ptr: *const UIWindow = window; - let ptr: *const WinitUIWindow = ptr.cast(); - &*ptr - }; - events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.id()), - event: WindowEvent::Destroyed, - })); - } - } - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_events(mtm, events); - app_state::terminated(mtm); + app_state::terminated(application); } #[method(applicationDidReceiveMemoryWarning:)] @@ -78,26 +58,3 @@ declare_class!( } } ); - -impl AppDelegate { - fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) { - let mut events = Vec::new(); - #[allow(deprecated)] - for window in application.windows().iter() { - if window.is_kind_of::() { - // SAFETY: We just checked that the window is a `winit` window - let window = unsafe { - let ptr: *const UIWindow = window; - let ptr: *const WinitUIWindow = ptr.cast(); - &*ptr - }; - events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.id()), - event: WindowEvent::Occluded(occluded), - })); - } - } - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_events(mtm, events); - } -} diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 630590ce2a..172ffe8e42 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -20,7 +20,7 @@ use objc2_foundation::{ CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion, NSProcessInfo, }; -use objc2_ui_kit::{UICoordinateSpace, UIView}; +use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow}; use super::window::WinitUIWindow; use crate::dpi::PhysicalSize; @@ -662,6 +662,28 @@ fn handle_user_events(mtm: MainThreadMarker) { } } +pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) { + let mtm = MainThreadMarker::from(application); + + let mut events = Vec::new(); + #[allow(deprecated)] + for window in application.windows().iter() { + if window.is_kind_of::() { + // SAFETY: We just checked that the window is a `winit` window + let window = unsafe { + let ptr: *const UIWindow = window; + let ptr: *const WinitUIWindow = ptr.cast(); + &*ptr + }; + events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.id()), + event: WindowEvent::Occluded(occluded), + })); + } + } + handle_nonuser_events(mtm, events); +} + pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); if !this.has_launched() || this.has_terminated() { @@ -696,7 +718,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) { AppState::get_mut(mtm).events_cleared_transition(); } -pub fn terminated(mtm: MainThreadMarker) { +pub(crate) fn terminated(application: &UIApplication) { + let mtm = MainThreadMarker::from(application); + + let mut events = Vec::new(); + #[allow(deprecated)] + for window in application.windows().iter() { + if window.is_kind_of::() { + // SAFETY: We just checked that the window is a `winit` window + let window = unsafe { + let ptr: *const UIWindow = window; + let ptr: *const WinitUIWindow = ptr.cast(); + &*ptr + }; + events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.id()), + event: WindowEvent::Destroyed, + })); + } + } + handle_nonuser_events(mtm, events); + let mut this = AppState::get_mut(mtm); let mut handler = this.terminated_transition(); drop(this); diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 1d1b21d7e3..38372f4297 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -4,7 +4,7 @@ use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use objc2_foundation::{MainThreadMarker, NSObject}; -use super::app_delegate::ApplicationDelegate; +use super::app_state::ApplicationDelegate; use crate::event::{DeviceEvent, ElementState}; declare_class!( diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_state.rs similarity index 80% rename from src/platform_impl/macos/app_delegate.rs rename to src/platform_impl/macos/app_state.rs index 08f1037ae6..ee149384b7 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_state.rs @@ -4,10 +4,9 @@ use std::rc::Weak; use std::time::Instant; use objc2::rc::Retained; -use objc2::runtime::AnyObject; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate}; -use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol}; +use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol}; use super::event_handler::EventHandler; use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo}; @@ -18,17 +17,8 @@ use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow}; use crate::window::WindowId as RootWindowId; #[derive(Debug)] -struct Policy(NSApplicationActivationPolicy); - -impl Default for Policy { - fn default() -> Self { - Self(NSApplicationActivationPolicy::Regular) - } -} - -#[derive(Debug, Default)] -pub(super) struct State { - activation_policy: Policy, +pub(super) struct AppState { + activation_policy: NSApplicationActivationPolicy, default_menu: bool, activate_ignoring_other_apps: bool, run_loop: RunLoop, @@ -63,62 +53,20 @@ declare_class!( } impl DeclaredClass for ApplicationDelegate { - type Ivars = State; + type Ivars = AppState; } unsafe impl NSObjectProtocol for ApplicationDelegate {} unsafe impl NSApplicationDelegate for ApplicationDelegate { - // NOTE: This will, globally, only be run once, no matter how many - // `EventLoop`s the user creates. #[method(applicationDidFinishLaunching:)] - fn did_finish_launching(&self, _sender: Option<&AnyObject>) { - trace_scope!("applicationDidFinishLaunching:"); - self.ivars().is_launched.set(true); - - let mtm = MainThreadMarker::from(self); - let app = NSApplication::sharedApplication(mtm); - // We need to delay setting the activation policy and activating the app - // until `applicationDidFinishLaunching` has been called. Otherwise the - // menu bar is initially unresponsive on macOS 10.15. - app.setActivationPolicy(self.ivars().activation_policy.0); - - window_activation_hack(&app); - #[allow(deprecated)] - app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps); - - if self.ivars().default_menu { - // The menubar initialization should be before the `NewEvents` event, to allow - // overriding of the default menu even if it's created - menu::initialize(&app); - } - - self.ivars().waker.borrow_mut().start(); - - self.set_is_running(true); - self.dispatch_init_events(); - - // If the application is being launched via `EventLoop::pump_app_events()` then we'll - // want to stop the app once it is launched (and return to the external loop) - // - // In this case we still want to consider Winit's `EventLoop` to be "running", - // so we call `start_running()` above. - if self.ivars().stop_on_launch.get() { - // NOTE: the original idea had been to only stop the underlying `RunLoop` - // for the app but that didn't work as expected (`-[NSApplication run]` - // effectively ignored the attempt to stop the RunLoop and re-started it). - // - // So we return from `pump_events` by stopping the application. - let app = NSApplication::sharedApplication(mtm); - stop_app_immediately(&app); - } + fn app_did_finish_launching(&self, notification: &NSNotification) { + self.did_finish_launching(notification) } #[method(applicationWillTerminate:)] - fn will_terminate(&self, _sender: Option<&AnyObject>) { - trace_scope!("applicationWillTerminate:"); - // TODO: Notify every window that it will be destroyed, like done in iOS? - self.internal_exit(); + fn app_will_terminate(&self, notification: &NSNotification) { + self.will_terminate(notification) } } ); @@ -130,16 +78,78 @@ impl ApplicationDelegate { default_menu: bool, activate_ignoring_other_apps: bool, ) -> Retained { - let this = mtm.alloc().set_ivars(State { - activation_policy: Policy(activation_policy), + let this = mtm.alloc().set_ivars(AppState { + activation_policy, default_menu, activate_ignoring_other_apps, run_loop: RunLoop::main(mtm), - ..Default::default() + event_handler: EventHandler::new(), + stop_on_launch: Cell::new(false), + stop_before_wait: Cell::new(false), + stop_after_wait: Cell::new(false), + stop_on_redraw: Cell::new(false), + is_launched: Cell::new(false), + is_running: Cell::new(false), + exit: Cell::new(false), + control_flow: Cell::new(ControlFlow::default()), + waker: RefCell::new(EventLoopWaker::new()), + start_time: Cell::new(None), + wait_timeout: Cell::new(None), + pending_redraw: RefCell::new(vec![]), }); unsafe { msg_send_id![super(this), init] } } + // NOTE: This will, globally, only be run once, no matter how many + // `EventLoop`s the user creates. + fn did_finish_launching(&self, _notification: &NSNotification) { + trace_scope!("applicationDidFinishLaunching:"); + self.ivars().is_launched.set(true); + + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + // We need to delay setting the activation policy and activating the app + // until `applicationDidFinishLaunching` has been called. Otherwise the + // menu bar is initially unresponsive on macOS 10.15. + app.setActivationPolicy(self.ivars().activation_policy); + + window_activation_hack(&app); + #[allow(deprecated)] + app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps); + + if self.ivars().default_menu { + // The menubar initialization should be before the `NewEvents` event, to allow + // overriding of the default menu even if it's created + menu::initialize(&app); + } + + self.ivars().waker.borrow_mut().start(); + + self.set_is_running(true); + self.dispatch_init_events(); + + // If the application is being launched via `EventLoop::pump_app_events()` then we'll + // want to stop the app once it is launched (and return to the external loop) + // + // In this case we still want to consider Winit's `EventLoop` to be "running", + // so we call `start_running()` above. + if self.ivars().stop_on_launch.get() { + // NOTE: the original idea had been to only stop the underlying `RunLoop` + // for the app but that didn't work as expected (`-[NSApplication run]` + // effectively ignored the attempt to stop the RunLoop and re-started it). + // + // So we return from `pump_events` by stopping the application. + let app = NSApplication::sharedApplication(mtm); + stop_app_immediately(&app); + } + } + + fn will_terminate(&self, _notification: &NSNotification) { + trace_scope!("applicationWillTerminate:"); + // TODO: Notify every window that it will be destroyed, like done in iOS? + self.internal_exit(); + } + pub fn get(mtm: MainThreadMarker) -> Retained { let app = NSApplication::sharedApplication(mtm); let delegate = diff --git a/src/platform_impl/macos/event_handler.rs b/src/platform_impl/macos/event_handler.rs index dbd89d7e83..5c353c1439 100644 --- a/src/platform_impl/macos/event_handler.rs +++ b/src/platform_impl/macos/event_handler.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::{fmt, mem}; -use super::app_delegate::HandlePendingUserEvents; +use super::app_state::HandlePendingUserEvents; use crate::event::Event; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; @@ -16,7 +16,7 @@ impl fmt::Debug for EventHandlerData { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub(crate) struct EventHandler { /// This can be in the following states: /// - Not registered by the event loop (None). @@ -26,6 +26,10 @@ pub(crate) struct EventHandler { } impl EventHandler { + pub(crate) const fn new() -> Self { + Self { inner: RefCell::new(None) } + } + /// Set the event loop handler for the duration of the given closure. /// /// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 6856b19d91..0fcd21363c 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -21,7 +21,7 @@ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; use super::app::WinitApplication; -use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents}; +use super::app_state::{ApplicationDelegate, HandlePendingUserEvents}; use super::event::dummy_event; use super::monitor::{self, MonitorHandle}; use super::observer::setup_control_flow_observers; diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 3049c9c863..2c3d3e3032 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -2,7 +2,7 @@ mod util; mod app; -mod app_delegate; +mod app_state; mod cursor; mod event; mod event_handler; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 732febc1b4..1f960d0a6b 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -22,7 +22,7 @@ use core_foundation::runloop::{ use objc2_foundation::MainThreadMarker; use tracing::error; -use super::app_delegate::ApplicationDelegate; +use super::app_state::ApplicationDelegate; use super::event_loop::{stop_app_on_panic, PanicInfo}; use super::ffi; @@ -251,8 +251,8 @@ impl Drop for EventLoopWaker { } } -impl Default for EventLoopWaker { - fn default() -> EventLoopWaker { +impl EventLoopWaker { + pub(crate) fn new() -> Self { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. @@ -268,12 +268,10 @@ impl Default for EventLoopWaker { ptr::null_mut(), ); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); - EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None } + Self { timer, start_instant: Instant::now(), next_fire_date: None } } } -} -impl EventLoopWaker { pub fn stop(&mut self) { if self.next_fire_date.is_some() { self.next_fire_date = None; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 8f9595b39e..50cffcee67 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -16,7 +16,7 @@ use objc2_foundation::{ NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use super::app_delegate::ApplicationDelegate; +use super::app_state::ApplicationDelegate; use super::cursor::{default_cursor, invisible_cursor}; use super::event::{ code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index c2d35ae7ab..425d6068df 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -22,7 +22,7 @@ use objc2_foundation::{ NSPoint, NSRect, NSSize, NSString, }; -use super::app_delegate::ApplicationDelegate; +use super::app_state::ApplicationDelegate; use super::cursor::cursor_from_icon; use super::monitor::{self, flip_window_screen_coordinates, get_display_id}; use super::observer::RunLoop;