rust-windowing / winit

Window handling library in pure Rust
https://docs.rs/winit/
Apache License 2.0
4.7k stars 886 forks source link

[iOS] Allow setting up a single UIWindow or UIViewController with either metal or glex context #802

Open seivan opened 5 years ago

seivan commented 5 years ago

Is there a way to side step Winit application setup for iOS and macOS and just get the building blocks for rendering Metal or openGL/ES?

I can setup the Application from Xcode and then call Rust code from Swift - so Winit can assume an application context is already setup. All I really need is either a UIWindow, UIView or a UIViewController from Winit.

I did a fork where this works, so there is nothing preventing Winit for taking arguments when initialising let window = winit::Window::new(args... &events_loop).unwrap(); to not setup the application context. In this case a UIApplicationMain, UIApplicationDelegate and the other parts that's not necessary.

Instead I rather send events down to Winit with some sort of of a push/pump style for application lifecycle.

Still expecting Winit to tackle touches and other window based events. The issue here is that those events (touches, window) are put on the UIApplicationDelegate when setting them on a UIViewController would suffice.

TL;DR would be nice to be able to side step these https://github.com/tomaka/winit/blob/master/src/platform/ios/mod.rs#L367-L368 and also implement the touch & window events on UIViewController instead of the UIApplicationDelegate. For most lifecycle events, there are NSNotificationCenter keys that can be used instead.

Osspial commented 5 years ago

cc @mtak-. I'd expect this could be handled by adding a WindowBuilderExt method.

Jasper-Bekkers commented 4 years ago

I ended up browsing iOS open issues to survey them a bit; but I think this issue can be closed since it's actually possible to do this, it's just badly documented (and may have been added after this issue got filed).

Call into the WindowBuilder like this

    #[cfg(target_os = "ios")]
    {
        use winit::platform::ios::WindowBuilderExtIOS;
        builder = builder.with_root_view_class(unsafe {
            get_ios_uiview_metal_sub_class() as *const _ as *const _
        });
    }

Create a metal enabled UIView like this:

/// On iOS we need to request the `CAMetalLayer` as part of the `UIView`, this is typically
/// done by subclassing a UIView and overriding the `layerClass` function to return
/// `CAMetalLayer::class`. Since the winit crate doesn't do this for us, we instead do it
/// ourselves. We create the `UIView` subclass and pass it to the `with_root_view_class`
/// function of `winit::WindowBuilderExtIOS`.
#[cfg(target_os = "ios")]
pub unsafe fn get_ios_uiview_metal_sub_class() -> &'static objc::runtime::Class {
    use objc::declare::ClassDecl;
    use objc::runtime::{Class, Sel};
    use objc::*;
    extern "C" fn layer_class(_: &Class, _: Sel) -> *const Class {
        class!(CAMetalLayer)
    }
    let mut decl = ClassDecl::new(&"MyUIView", class!(UIView))
        .expect("Failed to declare class `MyUIView`");
    decl.add_class_method(
        sel!(layerClass),
        layer_class as extern "C" fn(&Class, Sel) -> *const Class,
    );
    decl.register()
}

Edit: Just noticed that this doesn't address the specific technical goal in the issue; however it does get to the same goal, of getting metal up and running.

maxammann commented 2 years ago

This will be a requirement for the maplibre-rs project. We do not want to render maps fullscreen but in separate views. We also need handling of inputs. Not just Metal rendering.