diff options
Diffstat (limited to 'src/api/ios/mod.rs')
-rw-r--r-- | src/api/ios/mod.rs | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/src/api/ios/mod.rs b/src/api/ios/mod.rs new file mode 100644 index 0000000..9d1b527 --- /dev/null +++ b/src/api/ios/mod.rs @@ -0,0 +1,420 @@ +//! 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_glutin_app() { +//! start_inner() +//! } +//! +//! fn start_inner() { +//! ... +//! } +//! +//! ``` +//! +//! Compile project and then drag resulting .a into Xcode project. Add glutin.h to xcode. +//! +//! ```c +//! void start_glutin_app(); +//! ``` +//! +//! Use start_glutin_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 glutin: +//! +//! - applicationDidBecomeActive is Focused(true) +//! - applicationWillResignActive is Focused(false) +//! - applicationDidEnterBackground is Suspended(true) +//! - applicationWillEnterForeground is Suspended(false) +//! - applicationWillTerminate is Closed +//! +//! Keep in mind that after Closed event is received every attempt to draw with opengl will result in segfault. +//! +//! Also note that app will not receive Closed event if suspended, it will be SIGKILL'ed + + + + +#![cfg(target_os = "ios")] +#![deny(warnings)] + +use std::collections::VecDeque; +use std::ptr; +use std::mem; +use std::ffi::CString; + +use libc; +use objc::runtime::{Class, BOOL, YES, NO }; + +use native_monitor::NativeMonitorId; +use { Api, PixelFormat, CreationError, BuilderAttribs, GlContext, CursorState, MouseCursor, Event }; +use CreationError::OsError; + +mod delegate; +use self::delegate::{ create_delegate_class, create_view_class }; + +mod ffi; +use self::ffi::{ + gles, + setjmp, + dlopen, + dlsym, + UIApplicationMain, + kEAGLColorFormatRGB565, + CFTimeInterval, + CFRunLoopRunInMode, + kCFRunLoopDefaultMode, + kCFRunLoopRunHandledSource, + kEAGLDrawablePropertyRetainedBacking, + kEAGLDrawablePropertyColorFormat, + RTLD_LAZY, + RTLD_GLOBAL, + id, + nil, + NSString, + CGFloat + }; + + +static mut jmpbuf: [libc::c_int;27] = [0;27]; + +pub struct MonitorID; + +pub struct Window { + eagl_context: id, + delegate_state: *mut DelegateState +} + +#[derive(Clone)] +pub struct WindowProxy; + +pub struct PollEventsIterator<'a> { + window: &'a Window, +} + +pub struct WaitEventsIterator<'a> { + window: &'a Window, +} + +#[derive(Debug)] +struct DelegateState { + events_queue: VecDeque<Event>, + window: id, + controller: id, + view: id, + size: (u32,u32), + scale: f32 +} + + +impl DelegateState { + fn new(window: id, controller:id, view: id, size: (u32,u32), scale: f32) -> DelegateState { + DelegateState { + events_queue: VecDeque::new(), + window: window, + controller: controller, + view: view, + size: size, + scale: scale + } + } +} + + +pub fn get_available_monitors() -> VecDeque<MonitorID> { + let mut rb = VecDeque::new(); + rb.push_back(MonitorID); + rb +} + +pub fn get_primary_monitor() -> MonitorID { + MonitorID +} + +impl MonitorID { + pub fn get_name(&self) -> Option<String> { + Some("Primary".to_string()) + } + + pub fn get_native_identifier(&self) -> NativeMonitorId { + NativeMonitorId::Unavailable + } + + pub fn get_dimensions(&self) -> (u32, u32) { + unimplemented!() + } +} + + +impl Window { + + pub fn new(builder: BuilderAttribs) -> Result<Window, CreationError> { + unsafe { + if setjmp(mem::transmute(&mut jmpbuf)) != 0 { + let app: id = msg_send![Class::get("UIApplication").unwrap(), sharedApplication]; + let delegate: id = msg_send![app, delegate]; + let state: *mut libc::c_void = *(&*delegate).get_ivar("glutinState"); + let state = state as *mut DelegateState; + + let context = Window::create_context(); + + let mut window = Window { + eagl_context: context, + delegate_state: state + }; + + window.init_context(builder); + + return Ok(window) + } + } + + create_delegate_class(); + create_view_class(); + Window::start_app(); + + Err(CreationError::OsError(format!("Couldn't create UIApplication"))) + } + + unsafe fn init_context(&mut self, builder: BuilderAttribs) { + let draw_props: id = msg_send![Class::get("NSDictionary").unwrap(), alloc]; + let draw_props: id = msg_send![draw_props, + initWithObjects: + vec![ + msg_send![Class::get("NSNumber").unwrap(), numberWithBool: NO], + kEAGLColorFormatRGB565 + ].as_ptr() + forKeys: + vec![ + kEAGLDrawablePropertyRetainedBacking, + kEAGLDrawablePropertyColorFormat + ].as_ptr() + count: 2 + ]; + self.make_current(); + + let state = &mut *self.delegate_state; + + if builder.multitouch { + let _: () = msg_send![state.view, setMultipleTouchEnabled:YES]; + } + + let _: () = msg_send![state.view, setContentScaleFactor:state.scale as CGFloat]; + + let layer: id = msg_send![state.view, layer]; + let _: () = msg_send![layer, setContentsScale:state.scale as CGFloat]; + let _: () = msg_send![layer, setDrawableProperties: draw_props]; + + let gl = gles::Gles2::load_with(|symbol| self.get_proc_address(symbol)); + let mut color_render_buf: gles::types::GLuint = 0; + let mut frame_buf: gles::types::GLuint = 0; + gl.GenRenderbuffers(1, &mut color_render_buf); + gl.BindRenderbuffer(gles::RENDERBUFFER, color_render_buf); + + let ok: BOOL = msg_send![self.eagl_context, renderbufferStorage:gles::RENDERBUFFER fromDrawable:layer]; + if ok != YES { + panic!("EAGL: could not set renderbufferStorage"); + } + + gl.GenFramebuffers(1, &mut frame_buf); + gl.BindFramebuffer(gles::FRAMEBUFFER, frame_buf); + + gl.FramebufferRenderbuffer(gles::FRAMEBUFFER, gles::COLOR_ATTACHMENT0, gles::RENDERBUFFER, color_render_buf); + + let status = gl.CheckFramebufferStatus(gles::FRAMEBUFFER); + if gl.CheckFramebufferStatus(gles::FRAMEBUFFER) != gles::FRAMEBUFFER_COMPLETE { + panic!("framebuffer status: {:?}", status); + } + } + + fn create_context() -> id { + unsafe { + let eagl_context: id = msg_send![Class::get("EAGLContext").unwrap(), alloc]; + let eagl_context: id = msg_send![eagl_context, initWithAPI:2]; // es2 + eagl_context + } + } + + fn start_app() { + unsafe { + UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate")); + } + } + + pub fn is_closed(&self) -> bool { + false + } + + pub fn set_title(&self, _: &str) { + } + + pub fn show(&self) { + } + + pub fn hide(&self) { + } + + pub fn get_position(&self) -> Option<(i32, i32)> { + None + } + + pub fn set_position(&self, _x: i32, _y: i32) { + } + + pub fn get_inner_size(&self) -> Option<(u32, u32)> { + unsafe { Some((&*self.delegate_state).size) } + } + + pub fn get_outer_size(&self) -> Option<(u32, u32)> { + self.get_inner_size() + } + + pub fn set_inner_size(&self, _x: u32, _y: u32) { + } + + pub fn poll_events(&self) -> PollEventsIterator { + PollEventsIterator { + window: self + } + } + + pub fn wait_events(&self) -> WaitEventsIterator { + WaitEventsIterator { + window: self + } + } + + pub fn platform_display(&self) -> *mut libc::c_void { + unimplemented!(); + } + + pub fn platform_window(&self) -> *mut libc::c_void { + unimplemented!() + } + + pub fn get_pixel_format(&self) -> PixelFormat { + unimplemented!(); + } + + pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) { + } + + pub fn set_cursor(&self, _: MouseCursor) { + } + + pub fn set_cursor_state(&self, _: CursorState) -> Result<(), String> { + Ok(()) + } + + pub fn hidpi_factor(&self) -> f32 { + unsafe { (&*self.delegate_state) }.scale + } + + pub fn set_cursor_position(&self, _x: i32, _y: i32) -> Result<(), ()> { + unimplemented!(); + } + + pub fn create_window_proxy(&self) -> WindowProxy { + WindowProxy + } + +} + +impl GlContext for Window { + unsafe fn make_current(&self) { + let _:BOOL = msg_send![Class::get("EAGLContext").unwrap(), setCurrentContext: self.eagl_context]; + } + + fn is_current(&self) -> bool { + false + } + + fn get_proc_address(&self, addr: &str) -> *const libc::c_void { + let addr_c = CString::new(addr).unwrap(); + let path = CString::new("/System/Library/Frameworks/OpenGLES.framework/OpenGLES").unwrap(); + unsafe { + let lib = dlopen(path.as_ptr(), RTLD_LAZY | RTLD_GLOBAL); + dlsym(lib, addr_c.as_ptr()) + } + } + + fn swap_buffers(&self) { + unsafe { let _:BOOL = msg_send![self.eagl_context, presentRenderbuffer: gles::RENDERBUFFER]; } + } + + fn get_api(&self) -> Api { + unimplemented!() + } + + fn get_pixel_format(&self) -> PixelFormat { + unimplemented!() + } +} + +impl WindowProxy { + pub fn wakeup_event_loop(&self) { + unimplemented!() + } +} + + +impl<'a> Iterator for WaitEventsIterator<'a> { + type Item = Event; + + fn next(&mut self) -> Option<Event> { + loop { + if let Some(ev) = self.window.poll_events().next() { + return Some(ev); + } + } + } +} + +impl<'a> Iterator for PollEventsIterator<'a> { + type Item = Event; + + fn next(&mut self) -> Option<Event> { + unsafe { + let state = &mut *self.window.delegate_state; + + if let Some(event) = state.events_queue.pop_front() { + return Some(event) + } + + // jump hack, so we won't quit on willTerminate event before processing it + if setjmp(mem::transmute(&mut jmpbuf)) != 0 { + return state.events_queue.pop_front() + } + + // run runloop + let seconds: CFTimeInterval = 0.000002; + while CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, 1) == kCFRunLoopRunHandledSource {} + + state.events_queue.pop_front() + } + } +} |