Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS/iOS: Various refactorings in application state #3720

Merged
merged 3 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 7 additions & 50 deletions src/platform_impl/ios/app_delegate.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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::<WinitUIWindow>() {
// 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:)]
Expand All @@ -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::<WinitUIWindow>() {
// 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);
}
}
46 changes: 44 additions & 2 deletions src/platform_impl/ios/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<WinitUIWindow>() {
Comment on lines +670 to +671
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to filter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not at this time, it's a safety condition for the unsafe just below, and moving it to filter makes it seem like they're less connected.

// 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() {
Expand Down Expand Up @@ -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::<WinitUIWindow>() {
madsmtm marked this conversation as resolved.
Show resolved Hide resolved
// 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);
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/macos/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
}
);
Expand All @@ -130,16 +78,78 @@ impl ApplicationDelegate {
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
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<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
Expand Down
8 changes: 6 additions & 2 deletions src/platform_impl/macos/event_handler.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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).
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/macos/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
mod util;

mod app;
mod app_delegate;
mod app_state;
mod cursor;
mod event;
mod event_handler;
Expand Down
Loading
Loading