Skip to content

Instantly share code, notes, and snippets.

@seivan seivan/mod.rs
Created Oct 11, 2018

Embed
What would you like to do?
Events on UIViewController
//! iOS support
//!
//! # Building app
//! To build ios app you will need rustc built for this targets:
//!
//! - armv7-apple-ios
//! - armv7s-apple-ios
//! - i386-apple-ios
//! - aarch64-apple-ios
//! - x86_64-apple-ios
//!
//! Then
//!
//! ```
//! cargo build --target=...
//! ```
//! The simplest way to integrate your app into xcode environment is to build it
//! as a static library. Wrap your main function and export it.
//!
//! ```rust, ignore
//! #[no_mangle]
//! pub extern fn start_winit_app() {
//! start_inner()
//! }
//!
//! fn start_inner() {
//! ...
//! }
//!
//! ```
//!
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
//!
//! ```ignore
//! void start_winit_app();
//! ```
//!
//! Use start_winit_app inside your xcode's main function.
//!
//!
//! # App lifecycle and events
//!
//! iOS environment is very different from other platforms and you must be very
//! careful with it's events. Familiarize yourself with
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
//!
//!
//! This is how those event are represented in winit:
//!
//! - applicationDidBecomeActive is Focused(true)
//! - applicationWillResignActive is Focused(false)
//! - applicationDidEnterBackground is Suspended(true)
//! - applicationWillEnterForeground is Suspended(false)
//! - applicationWillTerminate is Destroyed
//!
//! Keep in mind that after Destroyed event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed
#![cfg(target_os = "ios")]
use std::cell::RefCell;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Arc;
use std::{fmt, mem, ptr};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL, YES};
use events::{Touch, TouchPhase};
use window::MonitorId as RootMonitorId;
use {
CreationError, Event, LogicalPosition, LogicalSize, MouseCursor, PhysicalPosition,
PhysicalSize, WindowAttributes, WindowEvent, WindowId as RootEventId,
};
mod ffi;
use self::ffi::{
id, kCFRunLoopDefaultMode, kCFRunLoopRunHandledSource, longjmp, nil, setjmp,
CFRunLoopRunInMode, CFTimeInterval, CGFloat, CGPoint, CGRect, JmpBuf, NSString,
UIApplicationMain, JBLEN,
};
static mut JMPBUF: Option<Box<JmpBuf>> = None;
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,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
}
}
}
#[derive(Clone)]
pub struct MonitorId;
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorId {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorId {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorId {
#[inline]
pub fn get_uiscreen(&self) -> id {
let class = class!(UIScreen);
unsafe { msg_send![class, mainScreen] }
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some("Primary".to_string())
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
let bounds: CGRect = unsafe { msg_send![self.get_uiscreen(), nativeBounds] };
(bounds.size.width as f64, bounds.size.height as f64).into()
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
// iOS assumes single screen
(0, 0).into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
let scale: CGFloat = unsafe { msg_send![self.get_uiscreen(), nativeScale] };
scale as f64
}
}
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(),
}
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
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)
}
}
pub fn run_forever<F>(&mut self, mut callback: F)
where
F: FnMut(::Event) -> ::ControlFlow,
{
// Yeah that's a very bad implementation.
loop {
let mut control_flow = ::ControlFlow::Continue;
self.poll_events(|e| {
if let ::ControlFlow::Break = callback(e) {
control_flow = ::ControlFlow::Break;
}
});
if let ::ControlFlow::Break = control_flow {
break;
}
::std::thread::sleep(::std::time::Duration::from_millis(5));
}
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy
}
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventsLoopClosed> {
unimplemented!()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
#[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)
}
}),
});
}
}
}
// TODO: AFAIK transparency is enabled by default on iOS,
// so to be consistent with other platforms we have to change that.
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 window: id = msg_send![window_class, alloc];
let window: id = msg_send![window, initWithFrame:bounds.clone()];
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 _: () = msg_send![window, setRootViewController: view_controller];
let _: () = msg_send![window, makeKeyAndVisible];
let _: () = msg_send![window, setBackgroundColor: green_color];
let size = (bounds.size.width as f64, bounds.size.height as f64).into();
let delegate_state = Box::new(DelegateState::new(window, size, scale as f64));
let app_class = class!(UIApplication);
let app: id = msg_send![app_class, sharedApplication];
println!("{:?}", app);
// unsafe {
// if setjmp(mem::transmute_copy(&mut JMPBUF)) != 0 {
// let app_class = class!(UIApplication);
// let app: id = msg_send![app_class, sharedApplication];
// let delegate: id = msg_send![app, delegate];
// let state: *mut c_void = *(&*delegate).get_ivar("winitState");
// let mut delegate_state = Box::from_raw(state as *mut DelegateState);
// let events_queue = &*ev.events_queue;
// (&mut *delegate).set_ivar("eventsQueue", mem::transmute::<_, *mut c_void>(events_queue));
// // easiest? way to get access to PlatformSpecificWindowBuilderAttributes to configure the view
// let rect: CGRect = msg_send![MonitorId.get_uiscreen(), bounds];
// let uiview_class = class!(UIView);
// let root_view_class = pl_attributes.root_view_class;
// let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
// assert!(is_uiview == YES, "`root_view_class` must inherit from `UIView`");
// delegate_state.view = msg_send![root_view_class, alloc];
// assert!(!delegate_state.view.is_null(), "Failed to create `UIView` instance");
// delegate_state.view = msg_send![delegate_state.view, initWithFrame:rect];
// assert!(!delegate_state.view.is_null(), "Failed to initialize `UIView` instance");
// let _: () = msg_send![delegate_state.controller, setView:delegate_state.view];
// let _: () = msg_send![delegate_state.window, makeKeyAndVisible];
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
}
#[inline]
pub fn set_title(&self, _title: &str) {
// N/A
}
#[inline]
pub fn show(&self) {
// N/A
}
#[inline]
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
// N/A
None
}
#[inline]
pub fn set_position(&self, _position: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
Some(self.delegate_state.size)
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_inner_size()
}
#[inline]
pub fn set_inner_size(&self, _size: LogicalSize) {
// N/A
}
#[inline]
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
// N/A
}
#[inline]
pub fn set_resizable(&self, _resizable: bool) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.delegate_state.scale
}
#[inline]
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
#[inline]
pub fn set_maximized(&self, _maximized: bool) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
// N/A
// iOS has single screen maximized apps so nothing to do
}
#[inline]
pub fn set_decorations(&self, _decorations: bool) {
// N/A
}
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
// N/A
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { inner: MonitorId }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorId> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorId);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorId {
MonitorId
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.