#![expect(non_snake_case, reason = "UIKit does not use Rust naming conventions")]
use std::cell::Cell;
use bevy_ecs::entity::Entity;
use bevy_ecs::query::{QuerySingleError, With};
use bevy_window::{
PrimaryWindow, Window, WindowActivate, WindowBackground, WindowCreated, WindowDeactivate,
WindowDestroyed, WindowForeground,
};
use objc2::rc::{Allocated, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, DefinedClass as _, MainThreadOnly, Message as _};
use objc2_foundation::{ns_string, NSNumber, NSObjectProtocol, NSSet};
use objc2_ui_kit::{
UICoordinateSpace, UIInterfaceOrientation, UIOpenURLContext, UIResponder, UIScene,
UISceneConnectionOptions, UISceneDelegate, UISceneSession, UITraitCollection, UIWindow,
UIWindowScene, UIWindowSceneDelegate,
};
use tracing::trace;
use crate::app::access_app;
use crate::windows::{setup_window, WorldHelper};
use crate::{UIKitWindows, USER_INFO_WINDOW_ENTITY_ID, WINDOW_ACTIVITY_TYPE};
pub(crate) struct Ivars {
entity: Cell<Option<Entity>>,
window: Cell<Option<Retained<UIWindow>>>,
}
define_class!(
#[unsafe(super(UIResponder))]
#[name = "BevySceneDelegate"]
#[ivars = Ivars]
pub(crate) struct SceneDelegate;
impl SceneDelegate {
#[unsafe(method_id(init))]
fn init(this: Allocated<Self>) -> Retained<Self> {
let this = this.set_ivars(Ivars {
entity: Cell::new(None),
window: Cell::new(None),
});
unsafe { msg_send![super(this), init] }
}
}
unsafe impl NSObjectProtocol for SceneDelegate {}
unsafe impl UISceneDelegate for SceneDelegate {
#[unsafe(method(scene:willConnectToSession:options:))]
fn scene_willConnectToSession_options(
&self,
scene: &UIScene,
session: &UISceneSession,
connection_options: &UISceneConnectionOptions,
) {
trace!(
scene = ?unsafe { scene.session().persistentIdentifier() },
user_info = ?unsafe { session.userInfo() },
configuration = ?unsafe { session.configuration() },
?connection_options,
"scene:willConnectToSession:options:"
);
let scene = scene.downcast_ref::<UIWindowScene>().unwrap();
let mut app = access_app(self.mtm());
let world = app.world_mut();
let entity = unsafe {
connection_options
.userActivities()
.iter()
.find(|activity| &*activity.activityType() == ns_string!(WINDOW_ACTIVITY_TYPE))
.and_then(|activity| activity.userInfo())
.and_then(|user_info| {
user_info.objectForKey(ns_string!(USER_INFO_WINDOW_ENTITY_ID))
})
.and_then(|obj| obj.downcast::<NSNumber>().ok())
.map(|number| Entity::from_bits(number.as_u64()))
};
let (entity, uikit_window) = if let Some(entity) = entity {
trace!("creating requested window");
let window = world
.get::<Window>(entity)
.expect("failed fetching Window component on newly created window");
let uikit_window = setup_window(Some(scene), entity, window, self.mtm());
(entity, uikit_window)
} else {
let query = world
.query_filtered::<Entity, With<PrimaryWindow>>()
.get_single(&world);
match query {
Ok(entity) => {
let uikit_windows = world.non_send_resource_mut::<UIKitWindows>();
if !uikit_windows.is_initialized(entity) {
trace!("initializing primary window");
let window = world.get::<Window>(entity).unwrap();
let uikit_window =
setup_window(Some(scene), entity, window, self.mtm());
(entity, uikit_window)
} else {
trace!("creating system-requested window");
let entity = world.spawn(Window::default());
let window = entity.get::<Window>().unwrap();
let uikit_window =
setup_window(Some(scene), entity.id(), window, self.mtm());
(entity.id(), uikit_window)
}
}
Err(QuerySingleError::NoEntities(_)) => {
trace!("creating primary window");
let entity = world.spawn((Window::default(), PrimaryWindow));
let window = entity.get::<Window>().unwrap();
let uikit_window =
setup_window(Some(scene), entity.id(), window, self.mtm());
(entity.id(), uikit_window)
}
Err(e) => panic!("failed fetching primary window: {e}"),
}
};
self.ivars().entity.set(Some(entity));
let uiwindow = uikit_window.uiwindow.retain().into_super();
self.ivars().window.set(Some(uiwindow));
world
.non_send_resource_mut::<UIKitWindows>()
.insert(entity, uikit_window);
world.send_window_event(WindowCreated { window: entity });
app.update();
}
#[unsafe(method(sceneWillEnterForeground:))]
fn sceneWillEnterForeground(&self, scene: &UIScene) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, "sceneWillEnterForeground:");
let mut app = access_app(self.mtm());
if let Some(window) = self.ivars().entity.get() {
app.world_mut()
.send_window_event(WindowForeground { window });
}
app.update();
}
#[unsafe(method(sceneDidBecomeActive:))]
fn sceneDidBecomeActive(&self, scene: &UIScene) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, "sceneDidBecomeActive:");
let mut app = access_app(self.mtm());
if let Some(window) = self.ivars().entity.get() {
app.world_mut().send_window_event(WindowActivate { window });
}
app.update();
}
#[unsafe(method(sceneWillResignActive:))]
fn sceneWillResignActive(&self, scene: &UIScene) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, "sceneWillResignActive:");
let mut app = access_app(self.mtm());
if let Some(window) = self.ivars().entity.get() {
app.world_mut()
.send_window_event(WindowDeactivate { window });
}
app.update();
}
#[unsafe(method(sceneDidEnterBackground:))]
fn sceneDidEnterBackground(&self, scene: &UIScene) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, "sceneDidEnterBackground:");
let mut app = access_app(self.mtm());
if let Some(window) = self.ivars().entity.get() {
app.world_mut()
.send_window_event(WindowBackground { window });
}
app.update();
}
#[unsafe(method(sceneDidDisconnect:))]
fn sceneDidDisconnect(&self, scene: &UIScene) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, "sceneDidDisconnect:");
let mut app = access_app(self.mtm());
if let Some(entity) = self.ivars().entity.get() {
let _ = app.world_mut().try_despawn(entity);
app.world_mut()
.send_window_event(WindowDestroyed { window: entity });
self.ivars().entity.set(None);
}
app.update();
}
#[unsafe(method(scene:openURLContexts:))]
fn scene_openURLContexts(&self, scene: &UIScene, url_contexts: &NSSet<UIOpenURLContext>) {
trace!(scene = ?unsafe { scene.session().persistentIdentifier() }, ?url_contexts, "scene:openURLContexts:");
}
}
unsafe impl UIWindowSceneDelegate for SceneDelegate {
#[unsafe(method_id(window))]
fn __window(&self) -> Option<Retained<UIWindow>> {
self.window()
}
#[unsafe(method(setWindow:))]
fn setWindow(&self, window: Option<&UIWindow>) {
self.ivars().window.set(window.map(|w| w.retain()));
}
#[unsafe(method(windowScene:didUpdateCoordinateSpace:interfaceOrientation:traitCollection:))]
fn windowScene_didUpdateCoordinateSpace_interfaceOrientation_traitCollection(
&self,
_scene: &UIWindowScene,
_previous_coordinate_space: &ProtocolObject<dyn UICoordinateSpace>,
_previous_interface_orientation: UIInterfaceOrientation,
_previous_trait_collection: &UITraitCollection,
) {
}
}
);
impl SceneDelegate {
fn window(&self) -> Option<Retained<UIWindow>> {
let window = self.ivars().window.take();
self.ivars().window.set(window.clone());
window
}
}