420 lines
11 KiB
Rust
420 lines
11 KiB
Rust
//! 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.window.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()
|
|
}
|
|
}
|
|
}
|