Allow the user to register the application delegate on macOS and iOS (#3758)
This allows the user to override the application delegate themselves, which opens several doors for customization that were previously closed. To do this, we use notifications instead of top-level application delegate methods. One effect of not providing an application delegate on iOS is that we no longer act as-if the application successfully open all URLs there. This is a breaking change, although unlikely to matter in practice, since the return value of `application:didFinishLaunchingWithOptions:` is seldom used by the system (and is likely the preferred behaviour anyhow).
This commit is contained in:
parent
3392e9c1de
commit
92e9bfe0fc
17 changed files with 445 additions and 269 deletions
|
|
@ -105,12 +105,12 @@ ndk = { version = "0.9.0", default-features = false }
|
||||||
|
|
||||||
# AppKit or UIKit
|
# AppKit or UIKit
|
||||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||||
|
block2 = "0.5.1"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
objc2 = "0.5.2"
|
objc2 = "0.5.2"
|
||||||
|
|
||||||
# AppKit
|
# AppKit
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
block2 = "0.5.1"
|
|
||||||
core-graphics = "0.23.1"
|
core-graphics = "0.23.1"
|
||||||
objc2-app-kit = { version = "0.2.2", features = [
|
objc2-app-kit = { version = "0.2.2", features = [
|
||||||
"NSAppearance",
|
"NSAppearance",
|
||||||
|
|
@ -152,6 +152,7 @@ objc2-foundation = { version = "0.2.2", features = [
|
||||||
"NSKeyValueObserving",
|
"NSKeyValueObserving",
|
||||||
"NSNotification",
|
"NSNotification",
|
||||||
"NSObjCRuntime",
|
"NSObjCRuntime",
|
||||||
|
"NSOperation",
|
||||||
"NSPathUtilities",
|
"NSPathUtilities",
|
||||||
"NSProcessInfo",
|
"NSProcessInfo",
|
||||||
"NSRunLoop",
|
"NSRunLoop",
|
||||||
|
|
@ -163,11 +164,13 @@ objc2-foundation = { version = "0.2.2", features = [
|
||||||
# UIKit
|
# UIKit
|
||||||
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
|
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
|
||||||
objc2-foundation = { version = "0.2.2", features = [
|
objc2-foundation = { version = "0.2.2", features = [
|
||||||
|
"block2",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"NSArray",
|
"NSArray",
|
||||||
"NSEnumerator",
|
"NSEnumerator",
|
||||||
"NSGeometry",
|
"NSGeometry",
|
||||||
"NSObjCRuntime",
|
"NSObjCRuntime",
|
||||||
|
"NSOperation",
|
||||||
"NSString",
|
"NSString",
|
||||||
"NSProcessInfo",
|
"NSProcessInfo",
|
||||||
"NSThread",
|
"NSThread",
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,10 @@ changelog entry.
|
||||||
- Changed how `ModifiersState` is serialized by Serde.
|
- Changed how `ModifiersState` is serialized by Serde.
|
||||||
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
|
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
|
||||||
- `MonitorHandle::position()` now returns an `Option`.
|
- `MonitorHandle::position()` now returns an `Option`.
|
||||||
|
- On iOS and macOS, remove custom application delegates. You are now allowed to override the
|
||||||
|
application delegate yourself.
|
||||||
|
- On iOS, no longer act as-if the application successfully open all URLs. Override
|
||||||
|
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
||||||
//! iOS 9.3.
|
//! iOS 9.3.
|
||||||
//!
|
//!
|
||||||
|
//! ## Window initialization
|
||||||
|
//!
|
||||||
//! iOS's main `UIApplicationMain` does some init work that's required by all
|
//! iOS's main `UIApplicationMain` does some init work that's required by all
|
||||||
//! UI-related code (see issue [#1705]). It is best to create your windows
|
//! UI-related code (see issue [#1705]). It is best to create your windows
|
||||||
//! inside `Event::Resumed`.
|
//! inside [`ApplicationHandler::resumed`].
|
||||||
//!
|
//!
|
||||||
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
|
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
|
||||||
|
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
|
||||||
//!
|
//!
|
||||||
//! ## Building app
|
//! ## Building app
|
||||||
//!
|
//!
|
||||||
|
|
@ -63,6 +66,16 @@
|
||||||
//! opengl will result in segfault.
|
//! opengl will result in segfault.
|
||||||
//!
|
//!
|
||||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
||||||
|
//!
|
||||||
|
//! ## Custom `UIApplicationDelegate`
|
||||||
|
//!
|
||||||
|
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||||
|
//! though, you might want to access some of the more niche stuff that [the application
|
||||||
|
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
|
||||||
|
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
|
||||||
|
//! register an application delegate, so you can set up a custom one in a nib file instead.
|
||||||
|
//!
|
||||||
|
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,84 @@
|
||||||
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
|
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
|
||||||
//! itself), and is regularly tested on macOS 10.14.
|
//! itself), and is regularly tested on macOS 10.14.
|
||||||
//!
|
//!
|
||||||
|
//! ## Window initialization
|
||||||
|
//!
|
||||||
//! A lot of functionality expects the application to be ready before you
|
//! A lot of functionality expects the application to be ready before you
|
||||||
//! start doing anything; this includes creating windows, fetching monitors,
|
//! start doing anything; this includes creating windows, fetching monitors,
|
||||||
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
|
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
|
||||||
//!
|
//!
|
||||||
//! If you encounter problems, you should try doing your initialization inside
|
//! If you encounter problems, you should try doing your initialization inside
|
||||||
//! `Event::Resumed`.
|
//! [`ApplicationHandler::resumed`].
|
||||||
//!
|
//!
|
||||||
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
|
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||||
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
|
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||||
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
|
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||||
|
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
|
||||||
|
//!
|
||||||
|
//! ## Custom `NSApplicationDelegate`
|
||||||
|
//!
|
||||||
|
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||||
|
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
|
||||||
|
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
|
||||||
|
//! increase the API surface by quite a lot.
|
||||||
|
//!
|
||||||
|
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
|
||||||
|
//!
|
||||||
|
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
|
||||||
|
//! to register your own application delegate, as outlined in the following example (see
|
||||||
|
//! `objc2-app-kit` for more detailed information).
|
||||||
|
#![cfg_attr(target_os = "macos", doc = "```")]
|
||||||
|
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
|
||||||
|
//! use objc2::rc::Retained;
|
||||||
|
//! use objc2::runtime::ProtocolObject;
|
||||||
|
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||||
|
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
|
||||||
|
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
|
||||||
|
//! use winit::event_loop::EventLoop;
|
||||||
|
//!
|
||||||
|
//! declare_class!(
|
||||||
|
//! struct AppDelegate;
|
||||||
|
//!
|
||||||
|
//! unsafe impl ClassType for AppDelegate {
|
||||||
|
//! type Super = NSObject;
|
||||||
|
//! type Mutability = mutability::MainThreadOnly;
|
||||||
|
//! const NAME: &'static str = "MyAppDelegate";
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl DeclaredClass for AppDelegate {}
|
||||||
|
//!
|
||||||
|
//! unsafe impl NSObjectProtocol for AppDelegate {}
|
||||||
|
//!
|
||||||
|
//! unsafe impl NSApplicationDelegate for AppDelegate {
|
||||||
|
//! #[method(application:openURLs:)]
|
||||||
|
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
|
||||||
|
//! // Note: To specifically get `application:openURLs:` to work, you _might_
|
||||||
|
//! // have to bundle your application. This is not done in this example.
|
||||||
|
//! println!("open urls: {application:?}, {urls:?}");
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! impl AppDelegate {
|
||||||
|
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
|
||||||
|
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
//! let event_loop = EventLoop::new()?;
|
||||||
|
//!
|
||||||
|
//! let mtm = MainThreadMarker::new().unwrap();
|
||||||
|
//! let delegate = AppDelegate::new(mtm);
|
||||||
|
//! // Important: Call `sharedApplication` after `EventLoop::new`,
|
||||||
|
//! // doing it before is not yet supported.
|
||||||
|
//! let app = NSApplication::sharedApplication(mtm);
|
||||||
|
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||||
|
//!
|
||||||
|
//! // event_loop.run_app(&mut my_app);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
#![allow(clippy::unnecessary_cast)]
|
#![allow(clippy::unnecessary_cast)]
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||||
|
|
||||||
use super::app_state::ApplicationDelegate;
|
use super::app_state::AppState;
|
||||||
use super::DEVICE_ID;
|
use super::DEVICE_ID;
|
||||||
use crate::event::{DeviceEvent, ElementState};
|
use crate::event::{DeviceEvent, ElementState};
|
||||||
|
|
||||||
|
|
@ -38,15 +40,15 @@ declare_class!(
|
||||||
key_window.sendEvent(event);
|
key_window.sendEvent(event);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
let app_state = AppState::get(MainThreadMarker::from(self));
|
||||||
maybe_dispatch_device_event(&delegate, event);
|
maybe_dispatch_device_event(&app_state, event);
|
||||||
unsafe { msg_send![super(self), sendEvent: event] }
|
unsafe { msg_send![super(self), sendEvent: event] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
|
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
|
||||||
let event_type = unsafe { event.r#type() };
|
let event_type = unsafe { event.r#type() };
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
match event_type {
|
match event_type {
|
||||||
|
|
@ -58,7 +60,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||||
let delta_y = unsafe { event.deltaY() } as f64;
|
let delta_y = unsafe { event.deltaY() } as f64;
|
||||||
|
|
||||||
if delta_x != 0.0 || delta_y != 0.0 {
|
if delta_x != 0.0 || delta_y != 0.0 {
|
||||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
|
||||||
delta: (delta_x, delta_y),
|
delta: (delta_x, delta_y),
|
||||||
});
|
});
|
||||||
|
|
@ -67,7 +69,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||||
},
|
},
|
||||||
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
|
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
|
||||||
let button = unsafe { event.buttonNumber() } as u32;
|
let button = unsafe { event.buttonNumber() } as u32;
|
||||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
||||||
button,
|
button,
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
|
|
@ -76,7 +78,7 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||||
},
|
},
|
||||||
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
|
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
|
||||||
let button = unsafe { event.buttonNumber() } as u32;
|
let button = unsafe { event.buttonNumber() } as u32;
|
||||||
delegate.maybe_queue_with_handler(move |app, event_loop| {
|
app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
|
||||||
button,
|
button,
|
||||||
state: ElementState::Released,
|
state: ElementState::Released,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, OnceCell, RefCell};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Weak;
|
use std::rc::{Rc, Weak};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use objc2::rc::Retained;
|
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};
|
||||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
use objc2_foundation::{MainThreadMarker, NSNotification};
|
||||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
|
|
||||||
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
|
|
||||||
|
|
||||||
use super::event_handler::EventHandler;
|
use super::event_handler::EventHandler;
|
||||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
|
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||||
|
|
@ -21,6 +19,7 @@ use crate::window::WindowId as RootWindowId;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct AppState {
|
pub(super) struct AppState {
|
||||||
|
mtm: MainThreadMarker,
|
||||||
activation_policy: NSApplicationActivationPolicy,
|
activation_policy: NSApplicationActivationPolicy,
|
||||||
default_menu: bool,
|
default_menu: bool,
|
||||||
activate_ignoring_other_apps: bool,
|
activate_ignoring_other_apps: bool,
|
||||||
|
|
@ -46,43 +45,32 @@ pub(super) struct AppState {
|
||||||
// as such should be careful to not add fields that, in turn, strongly reference those.
|
// as such should be careful to not add fields that, in turn, strongly reference those.
|
||||||
}
|
}
|
||||||
|
|
||||||
declare_class!(
|
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
|
||||||
#[derive(Debug)]
|
struct StaticMainThreadBound<T>(T);
|
||||||
pub(super) struct ApplicationDelegate;
|
|
||||||
|
|
||||||
unsafe impl ClassType for ApplicationDelegate {
|
impl<T> StaticMainThreadBound<T> {
|
||||||
type Super = NSObject;
|
const fn get(&self, _mtm: MainThreadMarker) -> &T {
|
||||||
type Mutability = mutability::MainThreadOnly;
|
&self.0
|
||||||
const NAME: &'static str = "WinitApplicationDelegate";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DeclaredClass for ApplicationDelegate {
|
unsafe impl<T> Send for StaticMainThreadBound<T> {}
|
||||||
type Ivars = AppState;
|
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
|
||||||
|
// main thread.
|
||||||
|
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
|
||||||
|
StaticMainThreadBound(OnceCell::new());
|
||||||
|
|
||||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
impl AppState {
|
||||||
#[method(applicationDidFinishLaunching:)]
|
pub(super) fn setup_global(
|
||||||
fn app_did_finish_launching(&self, notification: &NSNotification) {
|
|
||||||
self.did_finish_launching(notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillTerminate:)]
|
|
||||||
fn app_will_terminate(&self, notification: &NSNotification) {
|
|
||||||
self.will_terminate(notification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl ApplicationDelegate {
|
|
||||||
pub(super) fn new(
|
|
||||||
mtm: MainThreadMarker,
|
mtm: MainThreadMarker,
|
||||||
activation_policy: NSApplicationActivationPolicy,
|
activation_policy: NSApplicationActivationPolicy,
|
||||||
default_menu: bool,
|
default_menu: bool,
|
||||||
activate_ignoring_other_apps: bool,
|
activate_ignoring_other_apps: bool,
|
||||||
) -> Retained<Self> {
|
) -> Rc<Self> {
|
||||||
let this = mtm.alloc().set_ivars(AppState {
|
let this = Rc::new(AppState {
|
||||||
|
mtm,
|
||||||
activation_policy,
|
activation_policy,
|
||||||
proxy_wake_up: Arc::new(AtomicBool::new(false)),
|
proxy_wake_up: Arc::new(AtomicBool::new(false)),
|
||||||
default_menu,
|
default_menu,
|
||||||
|
|
@ -102,33 +90,42 @@ impl ApplicationDelegate {
|
||||||
wait_timeout: Cell::new(None),
|
wait_timeout: Cell::new(None),
|
||||||
pending_redraw: RefCell::new(vec![]),
|
pending_redraw: RefCell::new(vec![]),
|
||||||
});
|
});
|
||||||
unsafe { msg_send_id![super(this), init] }
|
|
||||||
|
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once");
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This will, globally, only be run once, no matter how many
|
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
|
||||||
// `EventLoop`s the user creates.
|
GLOBAL
|
||||||
fn did_finish_launching(&self, _notification: &NSNotification) {
|
.get(mtm)
|
||||||
trace_scope!("applicationDidFinishLaunching:");
|
.get()
|
||||||
self.ivars().is_launched.set(true);
|
.expect("tried to get application state before it was registered")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
let mtm = MainThreadMarker::from(self);
|
// NOTE: This notification will, globally, only be emitted once,
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
// no matter how many `EventLoop`s the user creates.
|
||||||
|
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
|
||||||
|
trace_scope!("NSApplicationDidFinishLaunchingNotification");
|
||||||
|
self.is_launched.set(true);
|
||||||
|
|
||||||
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
// We need to delay setting the activation policy and activating the app
|
// We need to delay setting the activation policy and activating the app
|
||||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||||
// menu bar is initially unresponsive on macOS 10.15.
|
// menu bar is initially unresponsive on macOS 10.15.
|
||||||
app.setActivationPolicy(self.ivars().activation_policy);
|
app.setActivationPolicy(self.activation_policy);
|
||||||
|
|
||||||
window_activation_hack(&app);
|
window_activation_hack(&app);
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
|
app.activateIgnoringOtherApps(self.activate_ignoring_other_apps);
|
||||||
|
|
||||||
if self.ivars().default_menu {
|
if self.default_menu {
|
||||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||||
// overriding of the default menu even if it's created
|
// overriding of the default menu even if it's created
|
||||||
menu::initialize(&app);
|
menu::initialize(&app);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ivars().waker.borrow_mut().start();
|
self.waker.borrow_mut().start();
|
||||||
|
|
||||||
self.set_is_running(true);
|
self.set_is_running(true);
|
||||||
self.dispatch_init_events();
|
self.dispatch_init_events();
|
||||||
|
|
@ -138,77 +135,65 @@ impl ApplicationDelegate {
|
||||||
//
|
//
|
||||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||||
// so we call `start_running()` above.
|
// so we call `start_running()` above.
|
||||||
if self.ivars().stop_on_launch.get() {
|
if self.stop_on_launch.get() {
|
||||||
// NOTE: the original idea had been to only stop the underlying `RunLoop`
|
// NOTE: the original idea had been to only stop the underlying `RunLoop`
|
||||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||||
//
|
//
|
||||||
// So we return from `pump_events` by stopping the application.
|
// So we return from `pump_events` by stopping the application.
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
stop_app_immediately(&app);
|
stop_app_immediately(&app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn will_terminate(&self, _notification: &NSNotification) {
|
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
|
||||||
trace_scope!("applicationWillTerminate:");
|
trace_scope!("NSApplicationWillTerminateNotification");
|
||||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||||
self.internal_exit();
|
self.internal_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
|
/// Place the event handler in the application state for the duration
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
|
||||||
let delegate =
|
|
||||||
unsafe { app.delegate() }.expect("a delegate was not configured on the application");
|
|
||||||
if delegate.is_kind_of::<Self>() {
|
|
||||||
// SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate`
|
|
||||||
unsafe { Retained::cast(delegate) }
|
|
||||||
} else {
|
|
||||||
panic!("tried to get a delegate that was not the one Winit has registered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Place the event handler in the application delegate for the duration
|
|
||||||
/// of the given closure.
|
/// of the given closure.
|
||||||
pub fn set_event_handler<R>(
|
pub fn set_event_handler<R>(
|
||||||
&self,
|
&self,
|
||||||
handler: &mut dyn ApplicationHandler,
|
handler: &mut dyn ApplicationHandler,
|
||||||
closure: impl FnOnce() -> R,
|
closure: impl FnOnce() -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
self.ivars().event_handler.set(handler, closure)
|
self.event_handler.set(handler, closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn proxy_wake_up(&self) -> Arc<AtomicBool> {
|
pub fn proxy_wake_up(&self) -> Arc<AtomicBool> {
|
||||||
self.ivars().proxy_wake_up.clone()
|
self.proxy_wake_up.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `pump_events` is called to progress the event loop then we
|
/// If `pump_events` is called to progress the event loop then we
|
||||||
/// bootstrap the event loop via `-[NSApplication run]` but will use
|
/// bootstrap the event loop via `-[NSApplication run]` but will use
|
||||||
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
|
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
|
||||||
pub fn set_stop_on_launch(&self) {
|
pub fn set_stop_on_launch(&self) {
|
||||||
self.ivars().stop_on_launch.set(true);
|
self.stop_on_launch.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stop_before_wait(&self, value: bool) {
|
pub fn set_stop_before_wait(&self, value: bool) {
|
||||||
self.ivars().stop_before_wait.set(value)
|
self.stop_before_wait.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stop_after_wait(&self, value: bool) {
|
pub fn set_stop_after_wait(&self, value: bool) {
|
||||||
self.ivars().stop_after_wait.set(value)
|
self.stop_after_wait.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stop_on_redraw(&self, value: bool) {
|
pub fn set_stop_on_redraw(&self, value: bool) {
|
||||||
self.ivars().stop_on_redraw.set(value)
|
self.stop_on_redraw.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_wait_timeout(&self, value: Option<Instant>) {
|
pub fn set_wait_timeout(&self, value: Option<Instant>) {
|
||||||
self.ivars().wait_timeout.set(value)
|
self.wait_timeout.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
|
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
|
||||||
///
|
///
|
||||||
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
|
||||||
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
/// and we won't need to re-launch the app if subsequent EventLoops are run.
|
||||||
pub fn internal_exit(&self) {
|
pub fn internal_exit(self: &Rc<Self>) {
|
||||||
self.with_handler(|app, event_loop| {
|
self.with_handler(|app, event_loop| {
|
||||||
app.exiting(event_loop);
|
app.exiting(event_loop);
|
||||||
});
|
});
|
||||||
|
|
@ -221,42 +206,41 @@ impl ApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_launched(&self) -> bool {
|
pub fn is_launched(&self) -> bool {
|
||||||
self.ivars().is_launched.get()
|
self.is_launched.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_is_running(&self, value: bool) {
|
pub fn set_is_running(&self, value: bool) {
|
||||||
self.ivars().is_running.set(value)
|
self.is_running.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_running(&self) -> bool {
|
pub fn is_running(&self) -> bool {
|
||||||
self.ivars().is_running.get()
|
self.is_running.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&self) {
|
pub fn exit(&self) {
|
||||||
self.ivars().exit.set(true)
|
self.exit.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_exit(&self) {
|
pub fn clear_exit(&self) {
|
||||||
self.ivars().exit.set(false)
|
self.exit.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exiting(&self) -> bool {
|
pub fn exiting(&self) -> bool {
|
||||||
self.ivars().exit.get()
|
self.exit.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_control_flow(&self, value: ControlFlow) {
|
pub fn set_control_flow(&self, value: ControlFlow) {
|
||||||
self.ivars().control_flow.set(value)
|
self.control_flow.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn control_flow(&self) -> ControlFlow {
|
pub fn control_flow(&self) -> ControlFlow {
|
||||||
self.ivars().control_flow.get()
|
self.control_flow.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_redraw(&self, window_id: WindowId) {
|
pub fn handle_redraw(self: &Rc<Self>, window_id: WindowId) {
|
||||||
let mtm = MainThreadMarker::from(self);
|
|
||||||
// Redraw request might come out of order from the OS.
|
// Redraw request might come out of order from the OS.
|
||||||
// -> Don't go back into the event handler when our callstack originates from there
|
// -> Don't go back into the event handler when our callstack originates from there
|
||||||
if !self.ivars().event_handler.in_use() {
|
if !self.event_handler.in_use() {
|
||||||
self.with_handler(|app, event_loop| {
|
self.with_handler(|app, event_loop| {
|
||||||
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
||||||
});
|
});
|
||||||
|
|
@ -264,24 +248,24 @@ impl ApplicationDelegate {
|
||||||
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
|
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
|
||||||
// events as a way to ensure that `pump_events` can't block an external loop
|
// events as a way to ensure that `pump_events` can't block an external loop
|
||||||
// indefinitely
|
// indefinitely
|
||||||
if self.ivars().stop_on_redraw.get() {
|
if self.stop_on_redraw.get() {
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
stop_app_immediately(&app);
|
stop_app_immediately(&app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_redraw(&self, window_id: WindowId) {
|
pub fn queue_redraw(&self, window_id: WindowId) {
|
||||||
let mut pending_redraw = self.ivars().pending_redraw.borrow_mut();
|
let mut pending_redraw = self.pending_redraw.borrow_mut();
|
||||||
if !pending_redraw.contains(&window_id) {
|
if !pending_redraw.contains(&window_id) {
|
||||||
pending_redraw.push(window_id);
|
pending_redraw.push(window_id);
|
||||||
}
|
}
|
||||||
self.ivars().run_loop.wakeup();
|
self.run_loop.wakeup();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn maybe_queue_with_handler(
|
pub fn maybe_queue_with_handler(
|
||||||
&self,
|
self: &Rc<Self>,
|
||||||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
|
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
|
||||||
) {
|
) {
|
||||||
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
|
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
|
||||||
|
|
@ -290,26 +274,28 @@ impl ApplicationDelegate {
|
||||||
// However, it is not documented which actions do this, and which ones are done immediately,
|
// However, it is not documented which actions do this, and which ones are done immediately,
|
||||||
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
|
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
|
||||||
// currently handling another event, and if we are, we queue the event instead.
|
// currently handling another event, and if we are, we queue the event instead.
|
||||||
if !self.ivars().event_handler.in_use() {
|
if !self.event_handler.in_use() {
|
||||||
self.with_handler(callback);
|
self.with_handler(callback);
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!("had to queue event since another is currently being handled");
|
tracing::debug!("had to queue event since another is currently being handled");
|
||||||
let this = self.retain();
|
let this = Rc::clone(self);
|
||||||
self.ivars().run_loop.queue_closure(move || {
|
self.run_loop.queue_closure(move || {
|
||||||
this.with_handler(callback);
|
this.with_handler(callback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn with_handler(&self, callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop)) {
|
fn with_handler(
|
||||||
let event_loop =
|
self: &Rc<Self>,
|
||||||
ActiveEventLoop { delegate: self.retain(), mtm: MainThreadMarker::from(self) };
|
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
|
||||||
self.ivars().event_handler.handle(callback, &event_loop);
|
) {
|
||||||
|
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
|
||||||
|
self.event_handler.handle(callback, &event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// dispatch `NewEvents(Init)` + `Resumed`
|
/// dispatch `NewEvents(Init)` + `Resumed`
|
||||||
pub fn dispatch_init_events(&self) {
|
pub fn dispatch_init_events(self: &Rc<Self>) {
|
||||||
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
|
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
|
||||||
// NB: For consistency all platforms must call `can_create_surfaces` even though macOS
|
// NB: For consistency all platforms must call `can_create_surfaces` even though macOS
|
||||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||||
|
|
@ -317,23 +303,22 @@ impl ApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by RunLoopObserver after finishing waiting for new events
|
// Called by RunLoopObserver after finishing waiting for new events
|
||||||
pub fn wakeup(&self, panic_info: Weak<PanicInfo>) {
|
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||||
let mtm = MainThreadMarker::from(self);
|
|
||||||
let panic_info = panic_info
|
let panic_info = panic_info
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||||
|
|
||||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||||
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
|
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ivars().stop_after_wait.get() {
|
if self.stop_after_wait.get() {
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
stop_app_immediately(&app);
|
stop_app_immediately(&app);
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = self.ivars().start_time.get().unwrap();
|
let start = self.start_time.get().unwrap();
|
||||||
let cause = match self.control_flow() {
|
let cause = match self.control_flow() {
|
||||||
ControlFlow::Poll => StartCause::Poll,
|
ControlFlow::Poll => StartCause::Poll,
|
||||||
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
|
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
|
||||||
|
|
@ -350,8 +335,7 @@ impl ApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by RunLoopObserver before waiting for new events
|
// Called by RunLoopObserver before waiting for new events
|
||||||
pub fn cleared(&self, panic_info: Weak<PanicInfo>) {
|
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
|
||||||
let mtm = MainThreadMarker::from(self);
|
|
||||||
let panic_info = panic_info
|
let panic_info = panic_info
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
.expect("The panic info must exist here. This failure indicates a developer error.");
|
||||||
|
|
@ -359,15 +343,15 @@ impl ApplicationDelegate {
|
||||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||||
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
|
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
|
if self.proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
|
||||||
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
|
||||||
}
|
}
|
||||||
|
|
||||||
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
|
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
|
||||||
for window_id in redraw {
|
for window_id in redraw {
|
||||||
self.with_handler(|app, event_loop| {
|
self.with_handler(|app, event_loop| {
|
||||||
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
|
||||||
|
|
@ -378,22 +362,22 @@ impl ApplicationDelegate {
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.exiting() {
|
if self.exiting() {
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
stop_app_immediately(&app);
|
stop_app_immediately(&app);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ivars().stop_before_wait.get() {
|
if self.stop_before_wait.get() {
|
||||||
let app = NSApplication::sharedApplication(mtm);
|
let app = NSApplication::sharedApplication(self.mtm);
|
||||||
stop_app_immediately(&app);
|
stop_app_immediately(&app);
|
||||||
}
|
}
|
||||||
self.ivars().start_time.set(Some(Instant::now()));
|
self.start_time.set(Some(Instant::now()));
|
||||||
let wait_timeout = self.ivars().wait_timeout.get(); // configured by pump_events
|
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
|
||||||
let app_timeout = match self.control_flow() {
|
let app_timeout = match self.control_flow() {
|
||||||
ControlFlow::Wait => None,
|
ControlFlow::Wait => None,
|
||||||
ControlFlow::Poll => Some(Instant::now()),
|
ControlFlow::Poll => Some(Instant::now()),
|
||||||
ControlFlow::WaitUntil(instant) => Some(instant),
|
ControlFlow::WaitUntil(instant) => Some(instant),
|
||||||
};
|
};
|
||||||
self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
|
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ impl EventHandler {
|
||||||
callback(*user_app, event_loop);
|
callback(*user_app, event_loop);
|
||||||
},
|
},
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// `NSApplication`, our app delegate and this handler are all
|
// `NSApplication`, our app state and this handler are all
|
||||||
// global state and so it's not impossible that we could get
|
// global state and so it's not impossible that we could get
|
||||||
// an event after the application has exited the `EventLoop`.
|
// an event after the application has exited the `EventLoop`.
|
||||||
tracing::error!("tried to run event handler, but no handler was set");
|
tracing::error!("tried to run event handler, but no handler was set");
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,16 @@ use core_foundation::runloop::{
|
||||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||||
};
|
};
|
||||||
use objc2::rc::{autoreleasepool, Retained};
|
use objc2::rc::{autoreleasepool, Retained};
|
||||||
use objc2::runtime::ProtocolObject;
|
|
||||||
use objc2::{msg_send_id, sel, ClassType};
|
use objc2::{msg_send_id, sel, ClassType};
|
||||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
|
use objc2_app_kit::{
|
||||||
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||||
|
NSApplicationWillTerminateNotification, NSWindow,
|
||||||
|
};
|
||||||
|
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
|
||||||
|
|
||||||
|
use super::super::notification_center::create_observer;
|
||||||
use super::app::WinitApplication;
|
use super::app::WinitApplication;
|
||||||
use super::app_state::ApplicationDelegate;
|
use super::app_state::AppState;
|
||||||
use super::cursor::CustomCursor;
|
use super::cursor::CustomCursor;
|
||||||
use super::event::dummy_event;
|
use super::event::dummy_event;
|
||||||
use super::monitor;
|
use super::monitor;
|
||||||
|
|
@ -71,15 +74,11 @@ impl PanicInfo {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ActiveEventLoop {
|
pub struct ActiveEventLoop {
|
||||||
pub(super) delegate: Retained<ApplicationDelegate>,
|
pub(super) app_state: Rc<AppState>,
|
||||||
pub(super) mtm: MainThreadMarker,
|
pub(super) mtm: MainThreadMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveEventLoop {
|
impl ActiveEventLoop {
|
||||||
pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
|
|
||||||
&self.delegate
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn hide_application(&self) {
|
pub(crate) fn hide_application(&self) {
|
||||||
NSApplication::sharedApplication(self.mtm).hide(None)
|
NSApplication::sharedApplication(self.mtm).hide(None)
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +98,7 @@ impl ActiveEventLoop {
|
||||||
|
|
||||||
impl RootActiveEventLoop for ActiveEventLoop {
|
impl RootActiveEventLoop for ActiveEventLoop {
|
||||||
fn create_proxy(&self) -> RootEventLoopProxy {
|
fn create_proxy(&self) -> RootEventLoopProxy {
|
||||||
let event_loop_proxy = EventLoopProxy::new(self.delegate.proxy_wake_up());
|
let event_loop_proxy = EventLoopProxy::new(self.app_state.proxy_wake_up());
|
||||||
RootEventLoopProxy { event_loop_proxy }
|
RootEventLoopProxy { event_loop_proxy }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,19 +139,19 @@ impl RootActiveEventLoop for ActiveEventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_control_flow(&self, control_flow: ControlFlow) {
|
fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||||
self.delegate.set_control_flow(control_flow)
|
self.app_state.set_control_flow(control_flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_flow(&self) -> ControlFlow {
|
fn control_flow(&self) -> ControlFlow {
|
||||||
self.delegate.control_flow()
|
self.app_state.control_flow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(&self) {
|
fn exit(&self) {
|
||||||
self.delegate.exit()
|
self.app_state.exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exiting(&self) -> bool {
|
fn exiting(&self) -> bool {
|
||||||
self.delegate.exiting()
|
self.app_state.exiting()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
|
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
|
||||||
|
|
@ -179,14 +178,17 @@ pub struct EventLoop {
|
||||||
/// We intentionally don't store `WinitApplication` since we want to have
|
/// We intentionally don't store `WinitApplication` since we want to have
|
||||||
/// the possibility of swapping that out at some point.
|
/// the possibility of swapping that out at some point.
|
||||||
app: Retained<NSApplication>,
|
app: Retained<NSApplication>,
|
||||||
/// The application delegate that we've registered.
|
app_state: Rc<AppState>,
|
||||||
///
|
|
||||||
/// The delegate is only weakly referenced by NSApplication, so we must
|
|
||||||
/// keep it around here as well.
|
|
||||||
delegate: Retained<ApplicationDelegate>,
|
|
||||||
|
|
||||||
window_target: ActiveEventLoop,
|
window_target: ActiveEventLoop,
|
||||||
panic_info: Rc<PanicInfo>,
|
panic_info: Rc<PanicInfo>,
|
||||||
|
|
||||||
|
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
|
||||||
|
// the system instead cleans it up next time it would have posted a notification to it.
|
||||||
|
//
|
||||||
|
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||||
|
_did_finish_launching_observer: Retained<NSObject>,
|
||||||
|
_will_terminate_observer: Retained<NSObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -229,25 +231,49 @@ impl EventLoop {
|
||||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
|
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
|
||||||
};
|
};
|
||||||
|
|
||||||
let delegate = ApplicationDelegate::new(
|
let app_state = AppState::setup_global(
|
||||||
mtm,
|
mtm,
|
||||||
activation_policy,
|
activation_policy,
|
||||||
attributes.default_menu,
|
attributes.default_menu,
|
||||||
attributes.activate_ignoring_other_apps,
|
attributes.activate_ignoring_other_apps,
|
||||||
);
|
);
|
||||||
|
|
||||||
autoreleasepool(|_| {
|
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||||
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
|
||||||
});
|
let weak_app_state = Rc::downgrade(&app_state);
|
||||||
|
let _did_finish_launching_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationDidFinishLaunching:`
|
||||||
|
unsafe { NSApplicationDidFinishLaunchingNotification },
|
||||||
|
move |notification| {
|
||||||
|
if let Some(app_state) = weak_app_state.upgrade() {
|
||||||
|
app_state.did_finish_launching(notification);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let weak_app_state = Rc::downgrade(&app_state);
|
||||||
|
let _will_terminate_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationWillTerminate:`
|
||||||
|
unsafe { NSApplicationWillTerminateNotification },
|
||||||
|
move |notification| {
|
||||||
|
if let Some(app_state) = weak_app_state.upgrade() {
|
||||||
|
app_state.will_terminate(notification);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let panic_info: Rc<PanicInfo> = Default::default();
|
let panic_info: Rc<PanicInfo> = Default::default();
|
||||||
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
||||||
|
|
||||||
Ok(EventLoop {
|
Ok(EventLoop {
|
||||||
app,
|
app,
|
||||||
delegate: delegate.clone(),
|
app_state: app_state.clone(),
|
||||||
window_target: ActiveEventLoop { delegate, mtm },
|
window_target: ActiveEventLoop { app_state, mtm },
|
||||||
panic_info,
|
panic_info,
|
||||||
|
_did_finish_launching_observer,
|
||||||
|
_will_terminate_observer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,19 +293,19 @@ impl EventLoop {
|
||||||
&mut self,
|
&mut self,
|
||||||
mut app: A,
|
mut app: A,
|
||||||
) -> Result<(), EventLoopError> {
|
) -> Result<(), EventLoopError> {
|
||||||
self.delegate.clear_exit();
|
self.app_state.clear_exit();
|
||||||
self.delegate.set_event_handler(&mut app, || {
|
self.app_state.set_event_handler(&mut app, || {
|
||||||
autoreleasepool(|_| {
|
autoreleasepool(|_| {
|
||||||
// clear / normalize pump_events state
|
// clear / normalize pump_events state
|
||||||
self.delegate.set_wait_timeout(None);
|
self.app_state.set_wait_timeout(None);
|
||||||
self.delegate.set_stop_before_wait(false);
|
self.app_state.set_stop_before_wait(false);
|
||||||
self.delegate.set_stop_after_wait(false);
|
self.app_state.set_stop_after_wait(false);
|
||||||
self.delegate.set_stop_on_redraw(false);
|
self.app_state.set_stop_on_redraw(false);
|
||||||
|
|
||||||
if self.delegate.is_launched() {
|
if self.app_state.is_launched() {
|
||||||
debug_assert!(!self.delegate.is_running());
|
debug_assert!(!self.app_state.is_running());
|
||||||
self.delegate.set_is_running(true);
|
self.app_state.set_is_running(true);
|
||||||
self.delegate.dispatch_init_events();
|
self.app_state.dispatch_init_events();
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: We do not run the application re-entrantly
|
// SAFETY: We do not run the application re-entrantly
|
||||||
|
|
@ -294,7 +320,7 @@ impl EventLoop {
|
||||||
resume_unwind(panic);
|
resume_unwind(panic);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.delegate.internal_exit()
|
self.app_state.internal_exit()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -306,47 +332,47 @@ impl EventLoop {
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
mut app: A,
|
mut app: A,
|
||||||
) -> PumpStatus {
|
) -> PumpStatus {
|
||||||
self.delegate.set_event_handler(&mut app, || {
|
self.app_state.set_event_handler(&mut app, || {
|
||||||
autoreleasepool(|_| {
|
autoreleasepool(|_| {
|
||||||
// As a special case, if the application hasn't been launched yet then we at least
|
// As a special case, if the application hasn't been launched yet then we at least
|
||||||
// run the loop until it has fully launched.
|
// run the loop until it has fully launched.
|
||||||
if !self.delegate.is_launched() {
|
if !self.app_state.is_launched() {
|
||||||
debug_assert!(!self.delegate.is_running());
|
debug_assert!(!self.app_state.is_running());
|
||||||
|
|
||||||
self.delegate.set_stop_on_launch();
|
self.app_state.set_stop_on_launch();
|
||||||
// SAFETY: We do not run the application re-entrantly
|
// SAFETY: We do not run the application re-entrantly
|
||||||
unsafe { self.app.run() };
|
unsafe { self.app.run() };
|
||||||
|
|
||||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
|
||||||
// has launched
|
// has launched
|
||||||
} else if !self.delegate.is_running() {
|
} else if !self.app_state.is_running() {
|
||||||
// Even though the application may have been launched, it's possible we aren't
|
// Even though the application may have been launched, it's possible we aren't
|
||||||
// running if the `EventLoop` was run before and has since
|
// running if the `EventLoop` was run before and has since
|
||||||
// exited. This indicates that we just starting to re-run
|
// exited. This indicates that we just starting to re-run
|
||||||
// the same `EventLoop` again.
|
// the same `EventLoop` again.
|
||||||
self.delegate.set_is_running(true);
|
self.app_state.set_is_running(true);
|
||||||
self.delegate.dispatch_init_events();
|
self.app_state.dispatch_init_events();
|
||||||
} else {
|
} else {
|
||||||
// Only run for as long as the given `Duration` allows so we don't block the
|
// Only run for as long as the given `Duration` allows so we don't block the
|
||||||
// external loop.
|
// external loop.
|
||||||
match timeout {
|
match timeout {
|
||||||
Some(Duration::ZERO) => {
|
Some(Duration::ZERO) => {
|
||||||
self.delegate.set_wait_timeout(None);
|
self.app_state.set_wait_timeout(None);
|
||||||
self.delegate.set_stop_before_wait(true);
|
self.app_state.set_stop_before_wait(true);
|
||||||
},
|
},
|
||||||
Some(duration) => {
|
Some(duration) => {
|
||||||
self.delegate.set_stop_before_wait(false);
|
self.app_state.set_stop_before_wait(false);
|
||||||
let timeout = Instant::now() + duration;
|
let timeout = Instant::now() + duration;
|
||||||
self.delegate.set_wait_timeout(Some(timeout));
|
self.app_state.set_wait_timeout(Some(timeout));
|
||||||
self.delegate.set_stop_after_wait(true);
|
self.app_state.set_stop_after_wait(true);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
self.delegate.set_wait_timeout(None);
|
self.app_state.set_wait_timeout(None);
|
||||||
self.delegate.set_stop_before_wait(false);
|
self.app_state.set_stop_before_wait(false);
|
||||||
self.delegate.set_stop_after_wait(true);
|
self.app_state.set_stop_after_wait(true);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.delegate.set_stop_on_redraw(true);
|
self.app_state.set_stop_on_redraw(true);
|
||||||
// SAFETY: We do not run the application re-entrantly
|
// SAFETY: We do not run the application re-entrantly
|
||||||
unsafe { self.app.run() };
|
unsafe { self.app.run() };
|
||||||
}
|
}
|
||||||
|
|
@ -360,8 +386,8 @@ impl EventLoop {
|
||||||
resume_unwind(panic);
|
resume_unwind(panic);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.delegate.exiting() {
|
if self.app_state.exiting() {
|
||||||
self.delegate.internal_exit();
|
self.app_state.internal_exit();
|
||||||
PumpStatus::Exit(0)
|
PumpStatus::Exit(0)
|
||||||
} else {
|
} else {
|
||||||
PumpStatus::Continue
|
PumpStatus::Continue
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use core_foundation::runloop::{
|
||||||
use objc2_foundation::MainThreadMarker;
|
use objc2_foundation::MainThreadMarker;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use super::app_state::ApplicationDelegate;
|
use super::app_state::AppState;
|
||||||
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ extern "C" fn control_flow_begin_handler(
|
||||||
match activity {
|
match activity {
|
||||||
kCFRunLoopAfterWaiting => {
|
kCFRunLoopAfterWaiting => {
|
||||||
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
// trace!("Triggered `CFRunLoopAfterWaiting`");
|
||||||
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
|
||||||
// trace!("Completed `CFRunLoopAfterWaiting`");
|
// trace!("Completed `CFRunLoopAfterWaiting`");
|
||||||
},
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -81,7 +81,7 @@ extern "C" fn control_flow_end_handler(
|
||||||
match activity {
|
match activity {
|
||||||
kCFRunLoopBeforeWaiting => {
|
kCFRunLoopBeforeWaiting => {
|
||||||
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
// trace!("Triggered `CFRunLoopBeforeWaiting`");
|
||||||
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
|
||||||
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
// trace!("Completed `CFRunLoopBeforeWaiting`");
|
||||||
},
|
},
|
||||||
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
|
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use objc2::rc::{Retained, WeakId};
|
use objc2::rc::{Retained, WeakId};
|
||||||
use objc2::runtime::{AnyObject, Sel};
|
use objc2::runtime::{AnyObject, Sel};
|
||||||
|
|
@ -16,7 +17,7 @@ use objc2_foundation::{
|
||||||
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::app_state::ApplicationDelegate;
|
use super::app_state::AppState;
|
||||||
use super::cursor::{default_cursor, invisible_cursor};
|
use super::cursor::{default_cursor, invisible_cursor};
|
||||||
use super::event::{
|
use super::event::{
|
||||||
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
||||||
|
|
@ -112,7 +113,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ViewState {
|
pub struct ViewState {
|
||||||
/// Strong reference to the global application state.
|
/// Strong reference to the global application state.
|
||||||
app_delegate: Retained<ApplicationDelegate>,
|
app_state: Rc<AppState>,
|
||||||
|
|
||||||
cursor_state: RefCell<CursorState>,
|
cursor_state: RefCell<CursorState>,
|
||||||
ime_position: Cell<NSPoint>,
|
ime_position: Cell<NSPoint>,
|
||||||
|
|
@ -206,7 +207,7 @@ declare_class!(
|
||||||
|
|
||||||
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
|
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
|
||||||
if let Some(window) = self.ivars()._ns_window.load() {
|
if let Some(window) = self.ivars()._ns_window.load() {
|
||||||
self.ivars().app_delegate.handle_redraw(window.id());
|
self.ivars().app_state.handle_redraw(window.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||||
|
|
@ -687,7 +688,7 @@ declare_class!(
|
||||||
|
|
||||||
self.update_modifiers(event, false);
|
self.update_modifiers(event, false);
|
||||||
|
|
||||||
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
|
||||||
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
|
||||||
);
|
);
|
||||||
self.queue_event(WindowEvent::MouseWheel {
|
self.queue_event(WindowEvent::MouseWheel {
|
||||||
|
|
@ -782,14 +783,14 @@ declare_class!(
|
||||||
|
|
||||||
impl WinitView {
|
impl WinitView {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
app_delegate: &ApplicationDelegate,
|
app_state: &Rc<AppState>,
|
||||||
window: &WinitWindow,
|
window: &WinitWindow,
|
||||||
accepts_first_mouse: bool,
|
accepts_first_mouse: bool,
|
||||||
option_as_alt: OptionAsAlt,
|
option_as_alt: OptionAsAlt,
|
||||||
) -> Retained<Self> {
|
) -> Retained<Self> {
|
||||||
let mtm = MainThreadMarker::from(window);
|
let mtm = MainThreadMarker::from(window);
|
||||||
let this = mtm.alloc().set_ivars(ViewState {
|
let this = mtm.alloc().set_ivars(ViewState {
|
||||||
app_delegate: app_delegate.retain(),
|
app_state: Rc::clone(app_state),
|
||||||
cursor_state: Default::default(),
|
cursor_state: Default::default(),
|
||||||
ime_position: Default::default(),
|
ime_position: Default::default(),
|
||||||
ime_size: Default::default(),
|
ime_size: Default::default(),
|
||||||
|
|
@ -834,7 +835,7 @@ impl WinitView {
|
||||||
|
|
||||||
fn queue_event(&self, event: WindowEvent) {
|
fn queue_event(&self, event: WindowEvent) {
|
||||||
let window_id = RootWindowId(self.window().id());
|
let window_id = RootWindowId(self.window().id());
|
||||||
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.window_event(event_loop, window_id, event);
|
app.window_event(event_loop, window_id, event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,8 @@ impl Window {
|
||||||
attributes: WindowAttributes,
|
attributes: WindowAttributes,
|
||||||
) -> Result<Self, RootOsError> {
|
) -> Result<Self, RootOsError> {
|
||||||
let mtm = window_target.mtm;
|
let mtm = window_target.mtm;
|
||||||
let delegate = autoreleasepool(|_| {
|
let delegate =
|
||||||
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
|
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
|
||||||
})?;
|
|
||||||
Ok(Window {
|
Ok(Window {
|
||||||
window: MainThreadBound::new(delegate.window().retain(), mtm),
|
window: MainThreadBound::new(delegate.window().retain(), mtm),
|
||||||
delegate: MainThreadBound::new(delegate, mtm),
|
delegate: MainThreadBound::new(delegate, mtm),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::cell::{Cell, RefCell};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use core_graphics::display::{CGDisplay, CGPoint};
|
use core_graphics::display::{CGDisplay, CGPoint};
|
||||||
|
|
@ -26,7 +27,7 @@ use objc2_foundation::{
|
||||||
};
|
};
|
||||||
use tracing::{trace, warn};
|
use tracing::{trace, warn};
|
||||||
|
|
||||||
use super::app_state::ApplicationDelegate;
|
use super::app_state::AppState;
|
||||||
use super::cursor::cursor_from_icon;
|
use super::cursor::cursor_from_icon;
|
||||||
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||||
use super::observer::RunLoop;
|
use super::observer::RunLoop;
|
||||||
|
|
@ -79,7 +80,7 @@ impl Default for PlatformSpecificWindowAttributes {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
/// Strong reference to the global application state.
|
/// Strong reference to the global application state.
|
||||||
app_delegate: Retained<ApplicationDelegate>,
|
app_state: Rc<AppState>,
|
||||||
|
|
||||||
window: Retained<WinitWindow>,
|
window: Retained<WinitWindow>,
|
||||||
|
|
||||||
|
|
@ -482,7 +483,7 @@ impl Drop for WindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_window(
|
fn new_window(
|
||||||
app_delegate: &ApplicationDelegate,
|
app_state: &Rc<AppState>,
|
||||||
attrs: &WindowAttributes,
|
attrs: &WindowAttributes,
|
||||||
mtm: MainThreadMarker,
|
mtm: MainThreadMarker,
|
||||||
) -> Option<Retained<WinitWindow>> {
|
) -> Option<Retained<WinitWindow>> {
|
||||||
|
|
@ -622,7 +623,7 @@ fn new_window(
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = WinitView::new(
|
let view = WinitView::new(
|
||||||
app_delegate,
|
app_state,
|
||||||
&window,
|
&window,
|
||||||
attrs.platform_specific.accepts_first_mouse,
|
attrs.platform_specific.accepts_first_mouse,
|
||||||
attrs.platform_specific.option_as_alt,
|
attrs.platform_specific.option_as_alt,
|
||||||
|
|
@ -665,11 +666,11 @@ fn new_window(
|
||||||
|
|
||||||
impl WindowDelegate {
|
impl WindowDelegate {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
app_delegate: &ApplicationDelegate,
|
app_state: &Rc<AppState>,
|
||||||
attrs: WindowAttributes,
|
attrs: WindowAttributes,
|
||||||
mtm: MainThreadMarker,
|
mtm: MainThreadMarker,
|
||||||
) -> Result<Retained<Self>, RootOsError> {
|
) -> Result<Retained<Self>, RootOsError> {
|
||||||
let window = new_window(app_delegate, &attrs, mtm)
|
let window = new_window(app_state, &attrs, mtm)
|
||||||
.ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?;
|
.ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?;
|
||||||
|
|
||||||
#[cfg(feature = "rwh_06")]
|
#[cfg(feature = "rwh_06")]
|
||||||
|
|
@ -709,7 +710,7 @@ impl WindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
let delegate = mtm.alloc().set_ivars(State {
|
let delegate = mtm.alloc().set_ivars(State {
|
||||||
app_delegate: app_delegate.retain(),
|
app_state: Rc::clone(app_state),
|
||||||
window: window.retain(),
|
window: window.retain(),
|
||||||
previous_position: Cell::new(None),
|
previous_position: Cell::new(None),
|
||||||
previous_scale_factor: Cell::new(scale_factor),
|
previous_scale_factor: Cell::new(scale_factor),
|
||||||
|
|
@ -808,7 +809,7 @@ impl WindowDelegate {
|
||||||
|
|
||||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||||
let window_id = RootWindowId(self.window().id());
|
let window_id = RootWindowId(self.window().id());
|
||||||
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.window_event(event_loop, window_id, event);
|
app.window_event(event_loop, window_id, event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -907,7 +908,7 @@ impl WindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
pub fn request_redraw(&self) {
|
||||||
self.ivars().app_delegate.queue_redraw(self.window().id());
|
self.ivars().app_state.queue_redraw(self.window().id());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod appkit;
|
mod appkit;
|
||||||
|
mod notification_center;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
mod uikit;
|
mod uikit;
|
||||||
|
|
||||||
|
|
|
||||||
27
src/platform_impl/apple/notification_center.rs
Normal file
27
src/platform_impl/apple/notification_center.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
use block2::RcBlock;
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
|
||||||
|
|
||||||
|
/// Observe the given notification.
|
||||||
|
///
|
||||||
|
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
|
||||||
|
/// give the user full control over those.
|
||||||
|
pub fn create_observer(
|
||||||
|
center: &NSNotificationCenter,
|
||||||
|
name: &NSNotificationName,
|
||||||
|
handler: impl Fn(&NSNotification) + 'static,
|
||||||
|
) -> Retained<NSObject> {
|
||||||
|
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
|
||||||
|
handler(unsafe { notification.as_ref() });
|
||||||
|
});
|
||||||
|
unsafe {
|
||||||
|
center.addObserverForName_object_queue_usingBlock(
|
||||||
|
Some(name),
|
||||||
|
None, // No sender filter
|
||||||
|
None, // No queue, run on posting thread (i.e. main thread)
|
||||||
|
&block,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
|
||||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
|
||||||
use objc2_ui_kit::UIApplication;
|
|
||||||
|
|
||||||
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
|
|
||||||
use crate::event::Event;
|
|
||||||
|
|
||||||
declare_class!(
|
|
||||||
pub struct AppDelegate;
|
|
||||||
|
|
||||||
unsafe impl ClassType for AppDelegate {
|
|
||||||
type Super = NSObject;
|
|
||||||
type Mutability = mutability::InteriorMutable;
|
|
||||||
const NAME: &'static str = "WinitApplicationDelegate";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeclaredClass for AppDelegate {}
|
|
||||||
|
|
||||||
// UIApplicationDelegate protocol
|
|
||||||
unsafe impl AppDelegate {
|
|
||||||
#[method(application:didFinishLaunchingWithOptions:)]
|
|
||||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
|
||||||
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidBecomeActive:)]
|
|
||||||
fn did_become_active(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillResignActive:)]
|
|
||||||
fn will_resign_active(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillEnterForeground:)]
|
|
||||||
fn will_enter_foreground(&self, application: &UIApplication) {
|
|
||||||
send_occluded_event_for_all_windows(application, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidEnterBackground:)]
|
|
||||||
fn did_enter_background(&self, application: &UIApplication) {
|
|
||||||
send_occluded_event_for_all_windows(application, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationWillTerminate:)]
|
|
||||||
fn will_terminate(&self, application: &UIApplication) {
|
|
||||||
app_state::terminated(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[method(applicationDidReceiveMemoryWarning:)]
|
|
||||||
fn did_receive_memory_warning(&self, _application: &UIApplication) {
|
|
||||||
let mtm = MainThreadMarker::new().unwrap();
|
|
||||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -12,11 +12,19 @@ use core_foundation::runloop::{
|
||||||
};
|
};
|
||||||
use objc2::rc::Retained;
|
use objc2::rc::Retained;
|
||||||
use objc2::{msg_send_id, ClassType};
|
use objc2::{msg_send_id, ClassType};
|
||||||
use objc2_foundation::{MainThreadMarker, NSString};
|
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
|
||||||
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
|
use objc2_ui_kit::{
|
||||||
|
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||||
|
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
|
||||||
|
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
|
||||||
|
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
|
||||||
|
UIApplicationWillTerminateNotification, UIScreen,
|
||||||
|
};
|
||||||
|
|
||||||
use super::app_delegate::AppDelegate;
|
use super::super::notification_center::create_observer;
|
||||||
use super::app_state::{AppState, EventLoopHandler};
|
use super::app_state::{
|
||||||
|
send_occluded_event_for_all_windows, AppState, EventLoopHandler, EventWrapper,
|
||||||
|
};
|
||||||
use super::{app_state, monitor, MonitorHandle};
|
use super::{app_state, monitor, MonitorHandle};
|
||||||
use crate::application::ApplicationHandler;
|
use crate::application::ApplicationHandler;
|
||||||
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError};
|
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError};
|
||||||
|
|
@ -149,6 +157,18 @@ fn map_user_event<'a, A: ApplicationHandler + 'a>(
|
||||||
pub struct EventLoop {
|
pub struct EventLoop {
|
||||||
mtm: MainThreadMarker,
|
mtm: MainThreadMarker,
|
||||||
window_target: ActiveEventLoop,
|
window_target: ActiveEventLoop,
|
||||||
|
|
||||||
|
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
|
||||||
|
// system instead cleans it up next time it would have posted a notification to it.
|
||||||
|
//
|
||||||
|
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||||
|
_did_finish_launching_observer: Retained<NSObject>,
|
||||||
|
_did_become_active_observer: Retained<NSObject>,
|
||||||
|
_will_resign_active_observer: Retained<NSObject>,
|
||||||
|
_will_enter_foreground_observer: Retained<NSObject>,
|
||||||
|
_did_enter_background_observer: Retained<NSObject>,
|
||||||
|
_will_terminate_observer: Retained<NSObject>,
|
||||||
|
_did_receive_memory_warning_observer: Retained<NSObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -173,7 +193,96 @@ impl EventLoop {
|
||||||
// this line sets up the main run loop before `UIApplicationMain`
|
// this line sets up the main run loop before `UIApplicationMain`
|
||||||
setup_control_flow_observers();
|
setup_control_flow_observers();
|
||||||
|
|
||||||
Ok(EventLoop { mtm, window_target: ActiveEventLoop { mtm } })
|
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||||
|
|
||||||
|
let _did_finish_launching_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `application:didFinishLaunchingWithOptions:`
|
||||||
|
unsafe { UIApplicationDidFinishLaunchingNotification },
|
||||||
|
move |_| {
|
||||||
|
app_state::did_finish_launching(mtm);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _did_become_active_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationDidBecomeActive:`
|
||||||
|
unsafe { UIApplicationDidBecomeActiveNotification },
|
||||||
|
move |_| {
|
||||||
|
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _will_resign_active_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationWillResignActive:`
|
||||||
|
unsafe { UIApplicationWillResignActiveNotification },
|
||||||
|
move |_| {
|
||||||
|
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _will_enter_foreground_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationWillEnterForeground:`
|
||||||
|
unsafe { UIApplicationWillEnterForegroundNotification },
|
||||||
|
move |notification| {
|
||||||
|
let app = unsafe { notification.object() }.expect(
|
||||||
|
"UIApplicationWillEnterForegroundNotification to have application object",
|
||||||
|
);
|
||||||
|
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
|
||||||
|
// documented to be `UIApplication`.
|
||||||
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||||
|
send_occluded_event_for_all_windows(&app, false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _did_enter_background_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationDidEnterBackground:`
|
||||||
|
unsafe { UIApplicationDidEnterBackgroundNotification },
|
||||||
|
move |notification| {
|
||||||
|
let app = unsafe { notification.object() }.expect(
|
||||||
|
"UIApplicationDidEnterBackgroundNotification to have application object",
|
||||||
|
);
|
||||||
|
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
|
||||||
|
// documented to be `UIApplication`.
|
||||||
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||||
|
send_occluded_event_for_all_windows(&app, true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _will_terminate_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationWillTerminate:`
|
||||||
|
unsafe { UIApplicationWillTerminateNotification },
|
||||||
|
move |notification| {
|
||||||
|
let app = unsafe { notification.object() }
|
||||||
|
.expect("UIApplicationWillTerminateNotification to have application object");
|
||||||
|
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
|
||||||
|
// (somewhat) documented to be `UIApplication`.
|
||||||
|
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||||
|
app_state::terminated(&app);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let _did_receive_memory_warning_observer = create_observer(
|
||||||
|
¢er,
|
||||||
|
// `applicationDidReceiveMemoryWarning:`
|
||||||
|
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
|
||||||
|
move |_| {
|
||||||
|
app_state::handle_nonuser_event(
|
||||||
|
mtm,
|
||||||
|
EventWrapper::StaticEvent(Event::MemoryWarning),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(EventLoop {
|
||||||
|
mtm,
|
||||||
|
window_target: ActiveEventLoop { mtm },
|
||||||
|
_did_finish_launching_observer,
|
||||||
|
_did_become_active_observer,
|
||||||
|
_will_resign_active_observer,
|
||||||
|
_will_enter_foreground_observer,
|
||||||
|
_did_enter_background_observer,
|
||||||
|
_will_terminate_observer,
|
||||||
|
_did_receive_memory_warning_observer,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||||
|
|
@ -199,9 +308,6 @@ impl EventLoop {
|
||||||
|
|
||||||
app_state::will_launch(self.mtm, handler);
|
app_state::will_launch(self.mtm, handler);
|
||||||
|
|
||||||
// Ensure application delegate is initialized
|
|
||||||
let _ = AppDelegate::class();
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// These functions are in crt_externs.h.
|
// These functions are in crt_externs.h.
|
||||||
fn _NSGetArgc() -> *mut c_int;
|
fn _NSGetArgc() -> *mut c_int;
|
||||||
|
|
@ -212,8 +318,10 @@ impl EventLoop {
|
||||||
UIApplicationMain(
|
UIApplicationMain(
|
||||||
*_NSGetArgc(),
|
*_NSGetArgc(),
|
||||||
NonNull::new(*_NSGetArgv()).unwrap(),
|
NonNull::new(*_NSGetArgv()).unwrap(),
|
||||||
|
// We intentionally override neither the application nor the delegate, to allow the
|
||||||
|
// user to do so themselves!
|
||||||
|
None,
|
||||||
None,
|
None,
|
||||||
Some(&NSString::from_str(AppDelegate::NAME)),
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#![allow(clippy::let_unit_value)]
|
#![allow(clippy::let_unit_value)]
|
||||||
|
|
||||||
mod app_delegate;
|
|
||||||
mod app_state;
|
mod app_state;
|
||||||
mod event_loop;
|
mod event_loop;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue