Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Change the way you get a window #33

Open
tomaka opened this issue Nov 3, 2016 · 6 comments
Open

Change the way you get a window #33

tomaka opened this issue Nov 3, 2016 · 6 comments
Labels
C - needs discussion Direction must be ironed out D - easy Likely easier than most tasks here H - help wanted Someone please save us P - low Nice to have S - api Design and usability

Comments

@tomaka
Copy link
Contributor

tomaka commented Nov 3, 2016

Original: rust-windowing/glutin#175

@tomaka tomaka added the S - api Design and usability label Nov 3, 2016
@francesca64 francesca64 added H - help wanted Someone please save us C - needs discussion Direction must be ironed out D - easy Likely easier than most tasks here P - low Nice to have labels May 6, 2018
@seivan
Copy link

seivan commented Oct 17, 2018

I agree with this, because this is a bit of an issue for iOS.
I got an issue where I need a game engine that uses Winit to work with the rest of the platform (macOS/iOS). The game engine in question uses Winit to set everything up (which btw is an amazing feature on its own). The problem is this won't work if you want it to coexist with existing applications.

An approach would be to let Winit remain is it is and set everything up for you - but also offer a way to give the lowest common denominator say a "window" for each platform.

For iOS, this could be a UIWindow, or a UIViewController or even just a UIView. Right now, it's a bit overbearing(?) for iOS where Winit sets up a whole application (UIApplicationMain & UIApplicatonDelegate) this work perfect if the goal is to be a single executable.

My issue here, if the game engine works that way, then it's going to be tricky to integrate with the rest of the platform. If you need to present other views or deal with In App Purchase transactions then the current Winit API doesn't work.

I am not asking to change the current approach, because there's a lot of value in that.
What I am suggesting is an approach where Winit (and Glutin) can work with creating components (say a UIView) for iOS. You could still offer the "all in one" way of setting up an application for platform, but could reuse code for setting up the lowest denominator component (calling it a Window). Because the existing iOS implementation for Winit still does all of that, but it's wrapped in also creating a whole application.

@seivan
Copy link

seivan commented Oct 17, 2018

I did a small proof of concept of getting Winit to work as a UViewController that I later present from Swift in an iOS application and it takes touch events. Removed a lot of code from iOS/mod.rs for brevity. Also pretty sure the CFRunLoopRunInMode stuff is unnecessary for this to work.

PS, I haven no clue how the jump stuff works or if they are necessary.
This works for single touch events by implementing touchesBegan:withEvent on the custom controller Winit creates. I can add others for app life cycles (using NSNotification) or even let Swift dispatch them to the game directly from the controller.

pub struct Window {
    _events_queue: Arc<RefCell<VecDeque<Event>>>,
    delegate_state: Box<DelegateState>,
}

unsafe impl Send for Window {}
unsafe impl Sync for Window {}

#[derive(Debug)]
struct DelegateState {
    window: id,
    size: LogicalSize,
    scale: f64,
}

impl DelegateState {
    fn new(window: id, size: LogicalSize, scale: f64) -> DelegateState {
        DelegateState {
            window,
            size,
            scale,
        }
    }
}



pub struct EventsLoop {
    events_queue: Arc<RefCell<VecDeque<Event>>>,
}

#[derive(Clone)]
pub struct EventsLoopProxy;

impl EventsLoop {
    pub fn new() -> EventsLoop {
        // unsafe {
        //     if !msg_send![class!(NSThread), isMainThread] {
        //         panic!("`EventsLoop` can only be created on the main thread on iOS");
        //     }
        // }
        EventsLoop {
            events_queue: Default::default(),
        }
    }



    pub fn poll_events<F>(&mut self, mut callback: F)
    where
        F: FnMut(::Event),
    {
        if let Some(event) = self.events_queue.borrow_mut().pop_front() {
            callback(event);
            return;
        }

        unsafe {
            // jump hack, so we won't quit on willTerminate event before processing it
            // assert!(
            //     JMPBUF.is_some(),
            //     "`EventsLoop::poll_events` must be called after window creation on iOS"
            // );
//            if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
                if let Some(event) = self.events_queue.borrow_mut().pop_front() {
                    callback(event);
                    return;
//                }
            }
        }

        unsafe {
            // run runloop
            let seconds: CFTimeInterval = 0.000002;
            while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1)
                == kCFRunLoopRunHandledSource
            {}
        }

        if let Some(event) = self.events_queue.borrow_mut().pop_front() {
            callback(event)
        }
    }


#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
    pub root_view_class: &'static Class,
}

impl Default for PlatformSpecificWindowBuilderAttributes {
    fn default() -> Self {
        PlatformSpecificWindowBuilderAttributes {
            root_view_class: class!(UIView),
        }
    }
}

        extern fn handle_touches(this: &Object, _: Sel, touches: id, _:id) {
        unsafe {
            let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
            let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);

            let touches_enum: id = msg_send![touches, objectEnumerator];

            loop {
                let touch: id = msg_send![touches_enum, nextObject];
                if touch == nil {
                    break
                }
                let location: CGPoint = msg_send![touch, locationInView:nil];
                let touch_id = touch as u64;
                let phase: i32 = msg_send![touch, phase];

                events_queue.borrow_mut().push_back(Event::WindowEvent {
                    window_id: RootEventId(WindowId),
                    event: WindowEvent::Touch(Touch {
                        device_id: DEVICE_ID,
                        id: touch_id,
                        location: (location.x as f64, location.y as f64).into(),
                        phase: match phase {
                            0 => TouchPhase::Started,
                            1 => TouchPhase::Moved,
                            // 2 is UITouchPhaseStationary and is not expected here
                            3 => TouchPhase::Ended,
                            4 => TouchPhase::Cancelled,
                            _ => panic!("unexpected touch phase: {:?}", phase)
                        }
                    }),
                });
            }
        }
    }

impl Window {

    
    pub fn new(
        ev: &EventsLoop,
        _attributes: WindowAttributes,
        pl_attributes: PlatformSpecificWindowBuilderAttributes,
    ) -> Result<Window, CreationError> {
        unsafe {
            // debug_assert!(mem::size_of_val(&JMPBUF) == mem::size_of::<Box<JmpBuf>>());
            // assert!(
            //     mem::replace(&mut JMPBUF, Some(Box::new([0; JBLEN]))).is_none(),
            //     "Only one `Window` is supported on iOS"
            // );

            let screen_class = class!(UIScreen);
            let window_class = class!(UIWindow);
            let controller_class = class!(UIViewController);
            let color_class = class!(UIColor);
            let green_color: id = msg_send![color_class, greenColor];
            let main_screen: id = msg_send![screen_class, mainScreen];
            let bounds: CGRect = msg_send![main_screen, bounds];
            let scale: CGFloat = msg_send![main_screen, nativeScale];


            let mut decl = ClassDecl::new("MyController", controller_class).expect("Failed to declare class `controller_class`");
            decl.add_method(sel!(touchesBegan:withEvent:),
                            handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
            decl.add_ivar::<*mut c_void>("eventsQueue");

            let decl = decl.register();

            let view_controller: id = msg_send![decl, alloc];
            let view_controller: id = msg_send![view_controller, init];
            let events_queue = &*ev.events_queue;

            (&mut *view_controller).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));

           let view: id =  msg_send![view_controller, view]; 

            let _: () = msg_send![view, setBackgroundColor: green_color];
            let size = (bounds.size.width as f64, bounds.size.height as f64).into();
            let delegate_state = Box::new(DelegateState::new(view_controller, size, scale as f64));


            return Ok(Window {
                _events_queue: ev.events_queue.clone(),
                delegate_state,
            });
        }
        //     }
        // }

        // // create_delegate_class();
        // // start_app();

        // panic!("Couldn't create `UIApplication`!")
    }

    #[inline]
    pub fn get_uiwindow(&self) -> id {
        self.delegate_state.window
    }

    #[inline]
    pub fn get_uiview(&self) -> id {
        self.delegate_state.window
    }

}

The client

pub struct GodObject {
    window: winit::Window,
    events_loop: winit::EventsLoop,

}

#[no_mangle]
pub extern fn run() -> *mut GodObject {
    let mut events_loop = winit::EventsLoop::new();
    let window = winit::Window::new(&events_loop).unwrap();    

    // events_loop.run_forever(|event| {
    //     ControlFlow::Continue
    // });
   let x = Box::new(GodObject { window, events_loop }) ;

   Box::into_raw(x)

}

#[no_mangle]
pub extern fn get_window(god_object: *const GodObject) -> *mut c_void {
    let god_object = unsafe { &*god_object };
    use winit::os::ios::WindowExt;  
    god_object.window.get_uiwindow()

}

#[no_mangle]
pub extern fn start_event_block(god_object: *mut GodObject) {
    let mut god_object = unsafe { &mut *god_object };
    use winit::os::ios::WindowExt;  
    let mut events_loop = &mut god_object.events_loop; 
    events_loop.poll_events(|event| {
        println!("{:?}", event);
        //ControlFlow::Continue
    });


}

And the Swift counter part

import UIKit
public class GameViewController: UIViewController {

    let game: OpaquePointer
    var displayLink: CADisplayLink? = nil
    
    public init() {
        self.game = run()!
        super.init(nibName: nil, bundle: nil)
    }
    
    @objc func tick(step: CADisplayLink) {
        start_event_block(self.game)
        print(step)
    }
    
    override public func viewDidLoad() {
        super.viewDidLoad()
        self.displayLink = CADisplayLink(target: self, selector: #selector(self.tick(step:)))
        let gameView = Unmanaged<UIViewController>.fromOpaque(get_window(self.game)!).takeRetainedValue()
        self.addChild(gameView)
        self.view.addSubview(gameView.view)
        gameView.didMove(toParent: self)
        
    }
    public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) }
    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.displayLink?.add(to: .current, forMode: .default)
    }
    public override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.displayLink?.invalidate()
        
    }
    public override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated)}
    

    override public var shouldAutorotate: Bool { return true }
    override public var prefersStatusBarHidden: Bool { return true }
    override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone { return .all } else { return .all }
    }
    
}

@sercand
Copy link

sercand commented Dec 19, 2020

This is exactly my use case. I want Winit to live in a UIView at my existing iOS application. At this current form, Winit handles so much job.

tmfink pushed a commit to tmfink/winit that referenced this issue Jan 5, 2022
enable early Z for SVG.

Additionally, this switches the B-quad patches for XCAA to be convex
hulls instead of bounding boxes, reducing fragment shader load.

This is a large speedup for the Ghostscript tiger demo: 5x or more.

Closes rust-windowing#33.
@notgull
Copy link
Member

notgull commented Feb 25, 2023

I think the ideal pattern here would be to have WindowBuilder::build() function return an error after creating the first window, or only allowing one Window to be active at once. In this case, the window attributes would be applied to the Window after being created.

@notgull
Copy link
Member

notgull commented May 2, 2023

Actually, I think that a better API would be to have a Window::get() function that returns an &'static Window that's available for all platforms, and then have an extension trait in the platform module to create new windows on desktop platforms.

@MarijnS95
Copy link
Member

On Android it should be possible to have multiple windows "open" within a single process (those are called Activities). Think about a share dialog that opens in some app, while that app also has a main window with something open. That window may not be visible, but it (typically) doesn't magically disappear (e.g. lose the page that was open).

On my phone a new Activity is also spawned when "converting" it to a free-form window or split-screen window, before the "old" one is closed (TODO: I'll have to check if that still happens when android:configChanges is set correctly to allow the app to handle simple resizes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C - needs discussion Direction must be ironed out D - easy Likely easier than most tasks here H - help wanted Someone please save us P - low Nice to have S - api Design and usability
Development

No branches or pull requests

7 participants