aura-gui 0.3.5

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Ciclo de vida UIScene de iOS (obligatorio con el SDK de iOS 27).
//!
//! winit 0.30 crea su `UIWindow` sin escena. Con el manifiesto de escena declarado en el `Info.plist` la app ya no
//! aborta al arrancar, pero esa ventana queda huérfana y se ve negra. Aquí se define el delegado de escena
//! `AuraSceneDelegate` (el nombre que va en el plist): al conectarse la escena se le asigna `windowScene` a la
//! ventana de winit y se vuelve a hacer visible. El orden entre escena y ventana da igual: el que llega segundo
//! completa la unión.
//!
//! Plantilla del plist: `docs/ios/Info.plist`.

use objc2::declare::ClassBuilder;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, AnyProtocol, NSObject, Sel};
use objc2::{ClassType, msg_send, msg_send_id, sel};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use std::cell::RefCell;
use std::sync::Once;

thread_local! {
    // Solo el hilo principal toca UIKit: no hace falta sincronizar.
    static SCENE: RefCell<Option<Retained<AnyObject>>> = const { RefCell::new(None) };
    static WINDOW: RefCell<Option<Retained<AnyObject>>> = const { RefCell::new(None) };
}

// Punteros crudos: el firmado con referencias no cumple `MethodImplementation` para todo tiempo de vida.
extern "C" fn will_connect(
    _this: *mut AnyObject,
    _cmd: Sel,
    scene: *mut AnyObject,
    _: *mut AnyObject,
    _: *mut AnyObject,
) {
    let scene = unsafe { Retained::retain(scene) };
    SCENE.with(|slot| *slot.borrow_mut() = scene);
    join();
}

/// Registra `AuraSceneDelegate` antes de que UIKit busque `UISceneDelegateClassName`; llamar antes de `run_app`.
/// UIKit exige que la clase declare conformidad con `UIWindowSceneDelegate` (que incluye `UISceneDelegate`).
pub fn register() {
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        let Some(mut builder) = ClassBuilder::new("AuraSceneDelegate", NSObject::class()) else { return };
        if let Some(protocol) = AnyProtocol::get("UIWindowSceneDelegate") {
            builder.add_protocol(protocol);
        }
        unsafe {
            builder.add_method(
                sel!(scene:willConnectToSession:options:),
                will_connect as extern "C" fn(*mut AnyObject, Sel, *mut AnyObject, *mut AnyObject, *mut AnyObject),
            );
        }
        builder.register();
    });
}

/// Guarda la ventana de winit para unirla a la escena (ya conectada o por conectarse).
pub fn adopt(window: &winit::window::Window) {
    let Ok(handle) = window.window_handle() else { return };
    let RawWindowHandle::UiKit(handle) = handle.as_raw() else { return };
    let view = unsafe { &*(handle.ui_view.as_ptr() as *const AnyObject) };
    let ui_window: Option<Retained<AnyObject>> = unsafe { msg_send_id![view, window] };
    WINDOW.with(|slot| *slot.borrow_mut() = ui_window);
    join();
}

fn join() {
    let (Some(scene), Some(window)) =
        (SCENE.with(|slot| slot.borrow().clone()), WINDOW.with(|slot| slot.borrow().clone()))
    else {
        return;
    };
    let current: Option<Retained<AnyObject>> = unsafe { msg_send_id![&*window, windowScene] };
    if current.is_none() {
        unsafe {
            let _: () = msg_send![&*window, setWindowScene: &*scene];
            let _: () = msg_send![&*window, makeKeyAndVisible];
        }
    }
}