El2.0 ios (#871)

* port ios winit el2.0 implementation to the new rust-windowing repo

* unimplemented! => unreachable
trailing comma in CFRunLoopTimerCallback

* implement get_fullscreen

* add iOS specific platform documentation. Add a TODO about how to possibly extend the iOS backend to work have methods callable from more than just the main thread

* assert that window is only dropped from the main thread

* assert_main_thread called from fewer places
This commit is contained in:
mtak- 2019-05-25 18:10:41 -07:00 committed by Osspial
parent e5aa906b01
commit 3a7350cbb9
10 changed files with 2478 additions and 695 deletions

View file

@ -94,6 +94,10 @@ impl Default for ControlFlow {
impl EventLoop<()> { impl EventLoop<()> {
/// Builds a new event loop with a `()` as the user event type. /// Builds a new event loop with a `()` as the user event type.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new() -> EventLoop<()> { pub fn new() -> EventLoop<()> {
EventLoop::<()>::new_user_event() EventLoop::<()>::new_user_event()
} }
@ -106,6 +110,10 @@ impl<T> EventLoop<T> {
/// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a wayland connection, and if it fails will /// If it is not set, winit will try to connect to a wayland connection, and if it fails will
/// fallback on x11. If this variable is set with any other value, winit will panic. /// fallback on x11. If this variable is set with any other value, winit will panic.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn new_user_event() -> EventLoop<T> { pub fn new_user_event() -> EventLoop<T> {
EventLoop { EventLoop {
event_loop: platform_impl::EventLoop::new(), event_loop: platform_impl::EventLoop::new(),

View file

@ -2,7 +2,21 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use {MonitorHandle, Window, WindowBuilder}; use event_loop::EventLoop;
use monitor::MonitorHandle;
use window::{Window, WindowBuilder};
/// Additional methods on `EventLoop` that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the idiom (phone/tablet/tv/etc) for the current device.
fn get_idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn get_idiom(&self) -> Idiom {
self.event_loop.get_idiom()
}
}
/// Additional methods on `Window` that are specific to iOS. /// Additional methods on `Window` that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
@ -11,10 +25,25 @@ pub trait WindowExtIOS {
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the `Window` is destroyed.
fn get_uiwindow(&self) -> *mut c_void; fn get_uiwindow(&self) -> *mut c_void;
/// Returns a pointer to the `UIViewController` that is used by this window.
///
/// The pointer will become invalid when the `Window` is destroyed.
fn get_uiviewcontroller(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window. /// Returns a pointer to the `UIView` that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the `Window` is destroyed.
fn get_uiview(&self) -> *mut c_void; fn get_uiview(&self) -> *mut c_void;
/// Sets the HiDpi factor used by this window.
///
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`.
fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for screens showing this `Window`.
///
/// On iPhones and iPods upside down portrait is never enabled.
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
} }
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
@ -23,10 +52,25 @@ impl WindowExtIOS for Window {
self.window.get_uiwindow() as _ self.window.get_uiwindow() as _
} }
#[inline]
fn get_uiviewcontroller(&self) -> *mut c_void {
self.window.get_uiviewcontroller() as _
}
#[inline] #[inline]
fn get_uiview(&self) -> *mut c_void { fn get_uiview(&self) -> *mut c_void {
self.window.get_uiview() as _ self.window.get_uiview() as _
} }
#[inline]
fn set_hidpi_factor(&self, hidpi_factor: f64) {
self.window.set_hidpi_factor(hidpi_factor)
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations)
}
} }
/// Additional methods on `WindowBuilder` that are specific to iOS. /// Additional methods on `WindowBuilder` that are specific to iOS.
@ -35,6 +79,15 @@ pub trait WindowBuilderExtIOS {
/// ///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]` /// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to `MonitorHandle::get_hidpi_factor()`.
fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the `Window`.
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
} }
impl WindowBuilderExtIOS for WindowBuilder { impl WindowBuilderExtIOS for WindowBuilder {
@ -43,6 +96,18 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) }; self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
self self
} }
#[inline]
fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder {
self.platform_specific.hidpi_factor = Some(hidpi_factor);
self
}
#[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
self.platform_specific.valid_orientations = valid_orientations;
self
}
} }
/// Additional methods on `MonitorHandle` that are specific to iOS. /// Additional methods on `MonitorHandle` that are specific to iOS.
@ -57,3 +122,40 @@ impl MonitorHandleExtIOS for MonitorHandle {
self.inner.get_uiscreen() as _ self.inner.get_uiscreen() as _
} }
} }
/// Valid orientations for a particular `Window`.
#[derive(Clone, Copy, Debug)]
pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone
LandscapeAndPortrait,
Landscape,
/// Excludes `PortraitUpsideDown` on iphone
Portrait,
}
impl Default for ValidOrientations {
#[inline]
fn default() -> ValidOrientations {
ValidOrientations::LandscapeAndPortrait
}
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}

View file

@ -0,0 +1,580 @@
use std::{mem, ptr};
use std::cell::{RefCell, RefMut};
use std::mem::ManuallyDrop;
use std::os::raw::c_void;
use std::time::Instant;
use event::{Event, StartCause};
use event_loop::ControlFlow;
use platform_impl::platform::event_loop::{EventHandler, Never};
use platform_impl::platform::ffi::{
id,
CFAbsoluteTimeGetCurrent,
CFRelease,
CFRunLoopAddTimer,
CFRunLoopGetMain,
CFRunLoopRef,
CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate,
CFRunLoopTimerRef,
CFRunLoopTimerSetNextFireDate,
kCFRunLoopCommonModes,
NSUInteger,
};
macro_rules! bug {
($msg:expr) => {
panic!("winit iOS bug, file an issue: {}", $msg)
};
}
// this is the state machine for the app lifecycle
#[derive(Debug)]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<id>,
queued_events: Vec<Event<Never>>,
},
Launching {
queued_windows: Vec<id>,
queued_events: Vec<Event<Never>>,
queued_event_handler: Box<EventHandler>,
},
ProcessingEvents {
event_handler: Box<EventHandler>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<Event<Never>>,
},
Waiting {
waiting_event_handler: Box<EventHandler>,
start: Instant,
},
PollFinished {
waiting_event_handler: Box<EventHandler>,
},
Terminated,
}
impl Drop for AppStateImpl {
fn drop(&mut self) {
match self {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } |
&mut AppStateImpl::Launching { ref mut queued_windows, .. } => unsafe {
for &mut window in queued_windows {
let () = msg_send![window, release];
}
}
_ => {}
}
}
}
pub struct AppState {
app_state: AppStateImpl,
control_flow: ControlFlow,
waker: EventLoopWaker,
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
if cfg!(debug_assertions) {
assert_main_thread!("bug in winit: `AppState::get_mut()` can only be called on the main thread");
}
let mut guard = APP_STATE.borrow_mut();
if guard.is_none() {
#[inline(never)]
#[cold]
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain());
**guard = Some(AppState {
app_state: AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
queued_events: Vec::new(),
},
control_flow: ControlFlow::default(),
waker,
});
}
init_guard(&mut guard)
}
RefMut::map(guard, |state| {
state.as_mut().unwrap()
})
}
// requires main thread and window is a UIWindow
// retains window
pub unsafe fn set_key_window(window: id) {
let mut this = AppState::get_mut();
match &mut this.app_state {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
queued_windows.push(window);
msg_send![window, retain];
return;
}
&mut AppStateImpl::ProcessingEvents { .. } => {},
&mut AppStateImpl::InUserCallback { .. } => {},
&mut AppStateImpl::Terminated => panic!("Attempt to create a `Window` \
after the app has terminated"),
app_state => unreachable!("unexpected state: {:#?}", app_state), // all other cases should be impossible
}
drop(this);
msg_send![window, makeKeyAndVisible]
}
// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<EventHandler>) {
let mut this = AppState::get_mut();
let (queued_windows, queued_events) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
ref mut queued_events,
} => {
let windows = ptr::read(queued_windows);
let events = ptr::read(queued_events);
(windows, events)
}
_ => panic!("winit iOS expected the app to be in a `NotLaunched` \
state, but was not - please file an issue"),
};
ptr::write(&mut this.app_state, AppStateImpl::Launching {
queued_windows,
queued_events,
queued_event_handler,
});
}
// requires main thread
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
let windows = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_windows,
..
} => mem::replace(queued_windows, Vec::new()),
_ => panic!(
"winit iOS expected the app to be in a `Launching` \
state, but was not - please file an issue"
),
};
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance.
// ```
let screen: id = msg_send![window, screen];
let () = msg_send![screen, retain];
let () = msg_send![window, setScreen:0 as id];
let () = msg_send![window, setScreen:screen];
let () = msg_send![screen, release];
let controller: id = msg_send![window, rootViewController];
let () = msg_send![window, setRootViewController:ptr::null::<()>()];
let () = msg_send![window, setRootViewController:controller];
let () = msg_send![window, makeKeyAndVisible];
}
let () = msg_send![window, release];
}
let mut this = AppState::get_mut();
let (windows, events, event_handler) = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_windows,
ref mut queued_events,
ref mut queued_event_handler,
} => {
let windows = ptr::read(queued_windows);
let events = ptr::read(queued_events);
let event_handler = ptr::read(queued_event_handler);
(windows, events, event_handler)
}
_ => panic!("winit iOS expected the app to be in a `Launching` \
state, but was not - please file an issue"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
});
drop(this);
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events);
AppState::handle_nonuser_events(events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
let () = msg_send![window, makeKeyAndVisible];
}
let () = msg_send![window, release];
}
}
// requires main thread
// AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() {
let mut this = AppState::get_mut();
let event = match this.control_flow {
ControlFlow::Poll => {
let event_handler = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::PollFinished {
ref mut waiting_event_handler,
} => ptr::read(waiting_event_handler),
_ => bug!("`EventHandler` unexpectedly started polling"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
});
Event::NewEvents(StartCause::Poll)
}
ControlFlow::Wait => {
let (event_handler, start) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::Waiting {
ref mut waiting_event_handler,
ref mut start,
} => (ptr::read(waiting_event_handler), *start),
_ => bug!("`EventHandler` unexpectedly woke up"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Wait,
});
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})
}
ControlFlow::WaitUntil(requested_resume) => {
let (event_handler, start) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } |
&mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::Waiting {
ref mut waiting_event_handler,
ref mut start,
} => (ptr::read(waiting_event_handler), *start),
_ => bug!("`EventHandler` unexpectedly woke up"),
};
ptr::write(&mut this.app_state, AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::WaitUntil(requested_resume),
});
if Instant::now() >= requested_resume {
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
})
} else {
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
})
}
}
ControlFlow::Exit => bug!("unexpected controlflow `Exit`"),
};
drop(this);
AppState::handle_nonuser_event(event)
}
// requires main thread
pub unsafe fn handle_nonuser_event(event: Event<Never>) {
AppState::handle_nonuser_events(std::iter::once(event))
}
// requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = Event<Never>>>(events: I) {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow) = match &mut this.app_state {
&mut AppStateImpl::Launching {
ref mut queued_events,
..
}
| &mut AppStateImpl::NotLaunched {
ref mut queued_events,
..
}
| &mut AppStateImpl::InUserCallback {
ref mut queued_events,
..
} => {
queued_events.extend(events);
return
}
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ptr::read(event_handler), *active_control_flow),
&mut AppStateImpl::PollFinished { .. }
| &mut AppStateImpl::Waiting { .. }
| &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"),
};
ptr::write(&mut this.app_state, AppStateImpl::InUserCallback {
queued_events: Vec::new(),
});
drop(this);
for event in events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
loop {
let mut this = AppState::get_mut();
let queued_events = match &mut this.app_state {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
} => mem::replace(queued_events, Vec::new()),
_ => bug!("unexpected `AppStateImpl`"),
};
if queued_events.is_empty() {
this.app_state = AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow,
};
this.control_flow = control_flow;
break
}
drop(this);
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
}
}
// requires main thread
pub unsafe fn handle_user_events() {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow) = match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ptr::read(event_handler), *active_control_flow),
&mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::PollFinished { .. }
| &mut AppStateImpl::Waiting { .. }
| &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"),
};
ptr::write(&mut this.app_state, AppStateImpl::InUserCallback {
queued_events: Vec::new(),
});
drop(this);
event_handler.handle_user_events(&mut control_flow);
loop {
let mut this = AppState::get_mut();
let queued_events = match &mut this.app_state {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
} => mem::replace(queued_events, Vec::new()),
_ => bug!("unexpected `AppStateImpl`"),
};
if queued_events.is_empty() {
this.app_state = AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow,
};
this.control_flow = control_flow;
break
}
drop(this);
for event in queued_events {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
event_handler.handle_user_events(&mut control_flow);
}
}
// requires main thread
pub unsafe fn handle_events_cleared() {
let mut this = AppState::get_mut();
match &mut this.app_state {
&mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return,
&mut AppStateImpl::ProcessingEvents { .. } => {}
_ => unreachable!(),
};
drop(this);
AppState::handle_user_events();
AppState::handle_nonuser_event(Event::EventsCleared);
let mut this = AppState::get_mut();
let (event_handler, old) = match &mut this.app_state {
&mut AppStateImpl::ProcessingEvents {
ref mut event_handler,
ref mut active_control_flow,
} => (ManuallyDrop::new(ptr::read(event_handler)), *active_control_flow),
_ => unreachable!(),
};
let new = this.control_flow;
match (old, new) {
(ControlFlow::Poll, ControlFlow::Poll) => {
ptr::write(
&mut this.app_state,
AppStateImpl::PollFinished {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
},
)
},
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
)
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
)
}
(_, ControlFlow::Wait) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
);
this.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
ptr::write(
&mut this.app_state,
AppStateImpl::Waiting {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
start,
},
);
this.waker.start_at(new_instant)
},
(_, ControlFlow::Poll) => {
ptr::write(
&mut this.app_state,
AppStateImpl::PollFinished {
waiting_event_handler: ManuallyDrop::into_inner(event_handler),
},
);
this.waker.start()
},
(_, ControlFlow::Exit) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
this.control_flow = old
}
}
}
pub fn terminated() {
let mut this = unsafe { AppState::get_mut() };
let mut old = mem::replace(&mut this.app_state, AppStateImpl::Terminated);
let mut control_flow = this.control_flow;
if let AppStateImpl::ProcessingEvents { ref mut event_handler, .. } = old {
drop(this);
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
} else {
bug!("`LoopDestroyed` happened while not processing events")
}
}
}
struct EventLoopWaker {
timer: CFRunLoopTimerRef,
}
impl Drop for EventLoopWaker {
fn drop(&mut self) {
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
fn new(rl: CFRunLoopRef) -> EventLoopWaker {
extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// create a timer with a 1microsec interval (1ns does not work) to mimic polling.
// it is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediatley in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();
} else {
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs =
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
}
}

View file

@ -0,0 +1,334 @@
use std::{mem, ptr};
use std::collections::VecDeque;
use std::ffi::c_void;
use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use std::sync::mpsc::{self, Sender, Receiver};
use event::Event;
use event_loop::{
ControlFlow,
EventLoopWindowTarget as RootEventLoopWindowTarget,
EventLoopClosed,
};
use platform::ios::Idiom;
use platform_impl::platform::app_state::AppState;
use platform_impl::platform::ffi::{
id,
nil,
CFIndex,
CFRelease,
CFRunLoopActivity,
CFRunLoopAddObserver,
CFRunLoopAddSource,
CFRunLoopGetMain,
CFRunLoopObserverCreate,
CFRunLoopObserverRef,
CFRunLoopSourceContext,
CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate,
CFRunLoopSourceRef,
CFRunLoopSourceSignal,
CFRunLoopWakeUp,
kCFRunLoopCommonModes,
kCFRunLoopDefaultMode,
kCFRunLoopEntry,
kCFRunLoopBeforeWaiting,
kCFRunLoopAfterWaiting,
kCFRunLoopExit,
NSOperatingSystemVersion,
NSString,
UIApplicationMain,
UIUserInterfaceIdiom,
};
use platform_impl::platform::monitor;
use platform_impl::platform::MonitorHandle;
use platform_impl::platform::view;
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
capabilities: Capabilities,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
}
pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> {
static mut SINGLETON_INIT: bool = false;
unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
assert!(!SINGLETON_INIT, "Only one `EventLoop` is supported on iOS. \
`EventLoopProxy` might be helpful");
SINGLETON_INIT = true;
view::create_delegate_class();
}
let (sender_to_clone, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let version: NSOperatingSystemVersion = unsafe {
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
msg_send![process_info, operatingSystemVersion]
};
let capabilities = version.into();
EventLoop {
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
sender_to_clone,
capabilities,
},
_marker: PhantomData,
}
}
}
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow)
{
unsafe {
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
assert_eq!(application, ptr::null_mut(), "\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS");
AppState::will_launch(Box::new(EventLoopHandler {
f: event_handler,
event_loop: self.window_target,
}));
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
unreachable!()
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread
unsafe {
monitor::uiscreens()
}
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
// guaranteed to be on main thread
unsafe {
monitor::main_uiscreen()
}
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
&self.window_target
}
}
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn get_idiom(&self) -> Idiom {
// guaranteed to be on main thread
unsafe {
self::get_idiom()
}
}
}
pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T> Send for EventLoopProxy<T> {}
unsafe impl<T> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
}
}
}
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wakeup the eventloop
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
// we want all the members of context to be zero/null, except one
let mut context: CFRunLoopSourceContext = mem::zeroed();
context.perform = event_loop_proxy_handler;
let source = CFRunLoopSourceCreate(
ptr::null_mut(),
CFIndex::max_value() - 1,
&mut context,
);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy {
sender,
source,
}
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> {
self.sender.send(event).map_err(|_| EventLoopClosed)?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other observers
extern fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(),
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopDestroyed will get sent after EventsCleared
extern fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
}
let main_loop = CFRunLoopGetMain();
let begin_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
control_flow_begin_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserverCreate(
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::max_value(),
control_flow_end_handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
}
}
#[derive(Debug)]
pub enum Never {}
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
}
struct EventLoopHandler<F, T: 'static> {
f: F,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
.finish()
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,
control_flow,
);
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() {
(self.f)(
Event::UserEvent(event),
&self.event_loop,
control_flow,
);
}
}
}
// must be called on main thread
pub unsafe fn get_idiom() -> Idiom {
let device: id = msg_send![class!(UIDevice), currentDevice];
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}
pub struct Capabilities {
pub supports_safe_area: bool,
}
impl From<NSOperatingSystemVersion> for Capabilities {
fn from(os_version: NSOperatingSystemVersion) -> Capabilities {
assert!(os_version.major >= 8, "`winit` current requires iOS version 8 or greater");
let supports_safe_area = os_version.major >= 11;
Capabilities { supports_safe_area }
}
}

View file

@ -1,24 +1,33 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::ffi::CString; use std::ffi::CString;
use std::ops::BitOr;
use std::os::raw::*; use std::os::raw::*;
use objc::{Encode, Encoding};
use objc::runtime::Object; use objc::runtime::Object;
use platform::ios::{Idiom, ValidOrientations};
pub type id = *mut Object; pub type id = *mut Object;
pub const nil: id = 0 as id; pub const nil: id = 0 as id;
pub type CFStringRef = *const c_void;
pub type CFTimeInterval = f64;
pub type Boolean = u32;
pub const kCFRunLoopRunHandledSource: i32 = 4;
#[cfg(target_pointer_width = "32")] #[cfg(target_pointer_width = "32")]
pub type CGFloat = f32; pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
pub type CGFloat = f64; pub type CGFloat = f64;
pub type NSInteger = isize;
pub type NSUInteger = usize;
#[repr(C)]
#[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion {
pub major: NSInteger,
pub minor: NSInteger,
pub patch: NSInteger,
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CGPoint { pub struct CGPoint {
@ -26,13 +35,6 @@ pub struct CGPoint {
pub y: CGFloat, pub y: CGFloat,
} }
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CGSize { pub struct CGSize {
@ -40,13 +42,134 @@ pub struct CGSize {
pub height: CGFloat, pub height: CGFloat,
} }
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
unsafe impl Encode for CGRect {
fn encode() -> Encoding {
unsafe {
if cfg!(target_pointer_width = "32") {
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
} else if cfg!(target_pointer_width = "64") {
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
} else {
unimplemented!()
}
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
fn encode() -> Encoding { NSInteger::encode() }
}
impl UIUserInterfaceIdiom {
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
}
impl From<Idiom> for UIUserInterfaceIdiom {
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
match idiom {
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
Idiom::Phone => UIUserInterfaceIdiom::Phone,
Idiom::Pad => UIUserInterfaceIdiom::Pad,
Idiom::TV => UIUserInterfaceIdiom::TV,
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
}
}
}
impl Into<Idiom> for UIUserInterfaceIdiom {
fn into(self) -> Idiom {
match self {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => unreachable!(),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UIInterfaceOrientationMask(NSUInteger);
unsafe impl Encode for UIInterfaceOrientationMask {
fn encode() -> Encoding { NSUInteger::encode() }
}
impl UIInterfaceOrientationMask {
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
pub const Landscape: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
pub const AllButUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
pub const All: UIInterfaceOrientationMask = UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
}
impl BitOr for UIInterfaceOrientationMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
UIInterfaceOrientationMask(self.0 | rhs.0)
}
}
impl UIInterfaceOrientationMask {
pub fn from_valid_orientations_idiom(
valid_orientations: ValidOrientations,
idiom: Idiom,
) -> UIInterfaceOrientationMask {
match (valid_orientations, idiom) {
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => UIInterfaceOrientationMask::AllButUpsideDown,
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
(ValidOrientations::Portrait, _) => UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown,
}
}
}
#[link(name = "UIKit", kind = "framework")] #[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "GlKit", kind = "framework")]
extern { extern {
pub static kCFRunLoopDefaultMode: CFStringRef; pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;
// int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
pub fn UIApplicationMain( pub fn UIApplicationMain(
argc: c_int, argc: c_int,
argv: *const c_char, argv: *const c_char,
@ -54,31 +177,115 @@ extern {
delegateClassName: id, delegateClassName: id,
) -> c_int; ) -> c_int;
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled ); pub fn CFRunLoopGetMain() -> CFRunLoopRef;
pub fn CFRunLoopRunInMode( pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
mode: CFStringRef,
seconds: CFTimeInterval, pub fn CFRunLoopObserverCreate(
returnAfterSourceHandled: Boolean, allocator: CFAllocatorRef,
) -> i32; activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
callout: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) -> CFRunLoopObserverRef;
pub fn CFRunLoopAddObserver(
rl: CFRunLoopRef,
observer: CFRunLoopObserverRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerCreate(
allocator: CFAllocatorRef,
fireDate: CFAbsoluteTime,
interval: CFTimeInterval,
flags: CFOptionFlags,
order: CFIndex,
callout: CFRunLoopTimerCallBack,
context: *mut CFRunLoopTimerContext,
) -> CFRunLoopTimerRef;
pub fn CFRunLoopAddTimer(
rl: CFRunLoopRef,
timer: CFRunLoopTimerRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopTimerSetNextFireDate(
timer: CFRunLoopTimerRef,
fireDate: CFAbsoluteTime,
);
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
pub fn CFRunLoopSourceCreate(
allocator: CFAllocatorRef,
order: CFIndex,
context: *mut CFRunLoopSourceContext,
) -> CFRunLoopSourceRef;
pub fn CFRunLoopAddSource(
rl: CFRunLoopRef,
source: CFRunLoopSourceRef,
mode: CFRunLoopMode,
);
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
pub fn CFRelease(cftype: *const c_void);
} }
extern { pub type Boolean = u8;
pub fn setjmp(env: *mut c_void) -> c_int; pub enum CFAllocator {}
pub fn longjmp(env: *mut c_void, val: c_int) -> !; pub type CFAllocatorRef = *mut CFAllocator;
pub enum CFRunLoop {}
pub type CFRunLoopRef = *mut CFRunLoop;
pub type CFRunLoopMode = CFStringRef;
pub enum CFRunLoopObserver {}
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
pub enum CFRunLoopTimer {}
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
pub enum CFRunLoopSource {}
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
pub enum CFString {}
pub type CFStringRef = *const CFString;
pub type CFHashCode = c_ulong;
pub type CFIndex = c_long;
pub type CFOptionFlags = c_ulong;
pub type CFRunLoopActivity = CFOptionFlags;
pub type CFAbsoluteTime = CFTimeInterval;
pub type CFTimeInterval = f64;
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
pub type CFRunLoopObserverCallBack = extern "C" fn(
observer: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
info: *mut c_void,
);
pub type CFRunLoopTimerCallBack = extern "C" fn(
timer: CFRunLoopTimerRef,
info: *mut c_void,
);
pub enum CFRunLoopObserverContext {}
pub enum CFRunLoopTimerContext {}
#[repr(C)]
pub struct CFRunLoopSourceContext {
pub version: CFIndex,
pub info: *mut c_void,
pub retain: extern "C" fn(*const c_void) -> *const c_void,
pub release: extern "C" fn(*const c_void),
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
pub perform: extern "C" fn(*mut c_void),
} }
// values taken from "setjmp.h" header in xcode iPhoneOS/iPhoneSimulator SDK
#[cfg(any(target_arch = "x86_64"))]
pub const JBLEN: usize = (9 * 2) + 3 + 16;
#[cfg(any(target_arch = "x86"))]
pub const JBLEN: usize = 18;
#[cfg(target_arch = "arm")]
pub const JBLEN: usize = 10 + 16 + 2;
#[cfg(target_arch = "aarch64")]
pub const JBLEN: usize = (14 + 8 + 2) * 2;
pub type JmpBuf = [c_int; JBLEN];
pub trait NSString: Sized { pub trait NSString: Sized {
unsafe fn alloc(_: Self) -> id { unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSString), alloc] msg_send![class!(NSString), alloc]

View file

@ -47,663 +47,55 @@
//! //!
//! This is how those event are represented in winit: //! This is how those event are represented in winit:
//! //!
//! - applicationDidBecomeActive is Focused(true) //! - applicationDidBecomeActive is Suspended(false)
//! - applicationWillResignActive is Focused(false) //! - applicationWillResignActive is Suspended(true)
//! - applicationDidEnterBackground is Suspended(true) //! - applicationWillTerminate is LoopDestroyed
//! - applicationWillEnterForeground is Suspended(false)
//! - applicationWillTerminate is Destroyed
//! //!
//! Keep in mind that after Destroyed event is received every attempt to draw with //! Keep in mind that after LoopDestroyed event is received every attempt to draw with
//! opengl will result in segfault. //! opengl will result in segfault.
//! //!
//! Also note that app will not receive Destroyed event if suspended, it will be SIGKILL'ed //! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
#![cfg(target_os = "ios")] #![cfg(target_os = "ios")]
use std::{fmt, mem, ptr}; // TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
use std::cell::RefCell; // worked around in the future by using GCD (grand central dispatch) and/or caching of values like
use std::collections::VecDeque; // window size/position.
use std::os::raw::*; macro_rules! assert_main_thread {
use std::sync::Arc; ($($t:tt)*) => {
if !msg_send![class!(NSThread), isMainThread] {
use objc::declare::ClassDecl; panic!($($t)*);
use objc::runtime::{BOOL, Class, Object, Sel, YES}; }
};
use { }
CreationError,
Event,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
WindowAttributes,
WindowEvent,
WindowId as RootEventId,
};
use events::{Touch, TouchPhase};
use window::MonitorHandle as RootMonitorHandle;
mod app_state;
mod event_loop;
mod ffi; mod ffi;
use self::ffi::{ mod monitor;
CFTimeInterval, mod view;
CFRunLoopRunInMode, mod window;
CGFloat,
CGPoint,
CGRect,
id,
JBLEN,
JmpBuf,
kCFRunLoopDefaultMode,
kCFRunLoopRunHandledSource,
longjmp,
nil,
NSString,
setjmp,
UIApplicationMain,
};
static mut JMPBUF: Option<Box<JmpBuf>> = None; pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use self::monitor::MonitorHandle;
pub struct Window { pub use self::window::{
_events_queue: Arc<RefCell<VecDeque<Event>>>, PlatformSpecificWindowBuilderAttributes,
delegate_state: Box<DelegateState>, Window,
} WindowId,
};
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
#[derive(Debug)]
struct DelegateState {
window: id,
controller: id,
view: id,
size: LogicalSize,
scale: f64,
}
impl DelegateState {
fn new(window: id, controller: id, view: id, size: LogicalSize, scale: f64) -> DelegateState {
DelegateState {
window,
controller,
view,
size,
scale,
}
}
}
impl Drop for DelegateState {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.window, release];
let _: () = msg_send![self.controller, release];
let _: () = msg_send![self.view, release];
}
}
}
#[derive(Clone)]
pub struct MonitorHandle;
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
#[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 EventLoop {
events_queue: Arc<RefCell<VecDeque<Event>>>,
}
#[derive(Clone)]
pub struct EventLoopProxy;
impl EventLoop {
pub fn new() -> EventLoop {
unsafe {
if !msg_send![class!(NSThread), isMainThread] {
panic!("`EventLoop` can only be created on the main thread on iOS");
}
}
EventLoop { events_queue: Default::default() }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
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(), "`EventLoop::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) -> EventLoopProxy {
EventLoopProxy
}
}
impl EventLoopProxy {
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> {
unimplemented!()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId; pub struct DeviceId {
uiscreen: ffi::id,
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId
}
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId { impl DeviceId {
pub unsafe fn dummy() -> Self { pub unsafe fn dummy() -> Self {
DeviceId DeviceId {
} uiscreen: std::ptr::null_mut(),
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
} }
} }
} }
// TODO: AFAIK transparency is enabled by default on iOS, unsafe impl Send for DeviceId {}
// so to be consistent with other platforms we have to change that. unsafe impl Sync for DeviceId {}
impl Window {
pub fn new(
ev: &EventLoop,
_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");
}
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![MonitorHandle.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.view
}
#[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 get_fullscreen(&self) -> Option<RootMonitorHandle> {
// N/A
// iOS has single screen maximized apps so nothing to do
None
}
#[inline]
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
// 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) -> RootMonitorHandle {
RootMonitorHandle { inner: MonitorHandle }
}
#[inline]
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut rb = VecDeque::with_capacity(1);
rb.push_back(MonitorHandle);
rb
}
#[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId
}
}
fn create_delegate_class() {
extern fn did_finish_launching(this: &mut Object, _: Sel, _: id, _: id) -> BOOL {
let screen_class = class!(UIScreen);
let window_class = class!(UIWindow);
let controller_class = class!(UIViewController);
unsafe {
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 size = (bounds.size.width as f64, bounds.size.height as f64).into();
let view_controller: id = msg_send![controller_class, alloc];
let view_controller: id = msg_send![view_controller, init];
let _: () = msg_send![window, setRootViewController:view_controller];
let state = Box::new(DelegateState::new(window, view_controller, ptr::null_mut(), size, scale as f64));
let state_ptr: *mut DelegateState = mem::transmute(state);
this.set_ivar("winitState", state_ptr as *mut c_void);
// The `UIView` is setup in `Window::new` which gets `longjmp`'ed to here.
// This makes it easier to configure the specific `UIView` type.
let _: () = msg_send![this, performSelector:sel!(postLaunch:) withObject:nil afterDelay:0.0];
}
YES
}
extern fn post_launch(_: &Object, _: Sel, _: id) {
unsafe { longjmp(mem::transmute_copy(&mut JMPBUF), 1); }
}
extern fn did_become_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(true),
});
}
}
extern fn will_resign_active(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Focused(false),
});
}
}
extern fn will_enter_foreground(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(false));
}
}
extern fn did_enter_background(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
events_queue.borrow_mut().push_back(Event::Suspended(true));
}
}
extern fn will_terminate(this: &Object, _: Sel, _: id) {
unsafe {
let events_queue: *mut c_void = *this.get_ivar("eventsQueue");
let events_queue = &*(events_queue as *const RefCell<VecDeque<Event>>);
// push event to the front to garantee that we'll process it
// immidiatly after jump
events_queue.borrow_mut().push_front(Event::WindowEvent {
window_id: RootEventId(WindowId),
event: WindowEvent::Destroyed,
});
longjmp(mem::transmute_copy(&mut JMPBUF), 1);
}
}
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)
}
}),
});
}
}
}
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
decl.add_method(sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id));
decl.add_method(sel!(touchesBegan:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesMoved:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesEnded:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesCancelled:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(postLaunch:),
post_launch as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<*mut c_void>("eventsQueue");
decl.register();
}
}
#[inline]
fn start_app() {
unsafe {
UIApplicationMain(0, ptr::null(), nil, NSString::alloc(nil).init_str("AppDelegate"));
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

View file

@ -0,0 +1,171 @@
use std::{
collections::VecDeque,
fmt,
ops::{Deref, DerefMut},
};
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::ffi::{
id,
nil,
CGFloat,
CGRect,
NSUInteger,
};
pub struct Inner {
uiscreen: id,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.uiscreen, release];
}
}
}
pub struct MonitorHandle {
inner: Inner,
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
}
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
unsafe {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
}
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle::retained_new(self.uiscreen)
}
}
impl Drop for MonitorHandle {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[derive(Debug)]
struct MonitorHandle {
name: Option<String>,
dimensions: PhysicalSize,
position: PhysicalPosition,
hidpi_factor: f64,
}
let monitor_id_proxy = MonitorHandle {
name: self.get_name(),
dimensions: self.get_dimensions(),
position: self.get_position(),
hidpi_factor: self.get_hidpi_factor(),
};
monitor_id_proxy.fmt(f)
}
}
impl MonitorHandle {
pub fn retained_new(uiscreen: id) -> MonitorHandle {
unsafe {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
let () = msg_send![uiscreen, retain];
}
MonitorHandle { inner: Inner { uiscreen } }
}
}
impl Inner {
pub fn get_name(&self) -> Option<String> {
unsafe {
if self.uiscreen == main_uiscreen().uiscreen {
Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen().uiscreen {
Some("Mirrored".to_string())
} else {
uiscreens()
.iter()
.position(|rhs| rhs.uiscreen == self.uiscreen)
.map(|idx| idx.to_string())
}
}
}
pub fn get_dimensions(&self) -> PhysicalSize {
unsafe {
let bounds: CGRect = msg_send![self.get_uiscreen(), nativeBounds];
(bounds.size.width as f64, bounds.size.height as f64).into()
}
}
pub fn get_position(&self) -> PhysicalPosition {
unsafe {
let bounds: CGRect = msg_send![self.get_uiscreen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
}
pub fn get_hidpi_factor(&self) -> f64 {
unsafe {
let scale: CGFloat = msg_send![self.get_uiscreen(), nativeScale];
scale as f64
}
}
}
// MonitorHandleExtIOS
impl Inner {
pub fn get_uiscreen(&self) -> id {
self.uiscreen
}
}
// requires being run on main thread
pub unsafe fn main_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
unsafe fn mirrored_uiscreen() -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen];
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
let screens: id = msg_send![class!(UIScreen), screens];
let count: NSUInteger = msg_send![screens, count];
let mut result = VecDeque::with_capacity(count as _);
let screens_enum: id = msg_send![screens, objectEnumerator];
loop {
let screen: id = msg_send![screens_enum, nextObject];
if screen == nil {
break result
}
result.push_back(MonitorHandle::retained_new(screen));
}
}

View file

@ -0,0 +1,395 @@
use std::collections::HashMap;
use objc::declare::ClassDecl;
use objc::runtime::{BOOL, Class, NO, Object, Sel, YES};
use event::{
DeviceId as RootDeviceId,
Event,
Touch,
TouchPhase,
WindowEvent
};
use platform::ios::MonitorHandleExtIOS;
use window::{WindowAttributes, WindowId as RootWindowId};
use platform_impl::platform::app_state::AppState;
use platform_impl::platform::DeviceId;
use platform_impl::platform::event_loop;
use platform_impl::platform::ffi::{
id,
nil,
CGFloat,
CGPoint,
CGRect,
UIInterfaceOrientationMask,
UITouchPhase,
};
use platform_impl::platform::window::{PlatformSpecificWindowBuilderAttributes};
// requires main thread
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
static mut ID: usize = 0;
if CLASSES.is_none() {
CLASSES = Some(HashMap::default());
}
let classes = CLASSES.as_mut().unwrap();
classes.entry(root_view_class).or_insert_with(move || {
let uiview_class = class!(UIView);
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass:uiview_class];
assert_eq!(is_uiview, YES, "`root_view_class` must inherit from `UIView`");
extern fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
unsafe {
let window: id = msg_send![object, window];
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::RedrawRequested,
});
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), drawRect: rect];
}
}
extern fn layout_subviews(object: &Object, _: Sel) {
unsafe {
let window: id = msg_send![object, window];
let bounds: CGRect = msg_send![window, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size),
});
let superclass: &'static Class = msg_send![object, superclass];
let () = msg_send![super(object, superclass), layoutSubviews];
}
}
let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class)
.expect("Failed to declare class `WinitUIView`");
ID += 1;
decl.add_method(sel!(drawRect:),
draw_rect as extern fn(&Object, Sel, CGRect));
decl.add_method(sel!(layoutSubviews),
layout_subviews as extern fn(&Object, Sel));
decl.register()
})
}
// requires main thread
unsafe fn get_view_controller_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiviewcontroller_class = class!(UIViewController);
extern fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
unsafe {
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
}
extern fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
unsafe {
*object.get_ivar::<BOOL>("_prefers_status_bar_hidden")
}
}
extern fn set_supported_orientations(object: &mut Object, _: Sel, orientations: UIInterfaceOrientationMask) {
unsafe {
object.set_ivar::<UIInterfaceOrientationMask>("_supported_orientations", orientations);
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
}
extern fn supported_orientations(object: &Object, _: Sel) -> UIInterfaceOrientationMask {
unsafe {
*object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations")
}
}
extern fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES
}
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`");
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
decl.add_method(sel!(setPrefersStatusBarHidden:),
set_prefers_status_bar_hidden as extern fn(&mut Object, Sel, BOOL));
decl.add_method(sel!(prefersStatusBarHidden),
prefers_status_bar_hidden as extern fn(&Object, Sel) -> BOOL);
decl.add_method(sel!(setSupportedInterfaceOrientations:),
set_supported_orientations as extern fn(&mut Object, Sel, UIInterfaceOrientationMask));
decl.add_method(sel!(supportedInterfaceOrientations),
supported_orientations as extern fn(&Object, Sel) -> UIInterfaceOrientationMask);
decl.add_method(sel!(shouldAutorotate),
should_autorotate as extern fn(&Object, Sel) -> BOOL);
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
unsafe fn get_window_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
let uiwindow_class = class!(UIWindow);
extern fn become_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(true),
});
let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
}
}
extern fn resign_key_window(object: &Object, _: Sel) {
unsafe {
AppState::handle_nonuser_event(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Focused(false),
});
let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
}
}
extern fn handle_touches(object: &Object, _: Sel, touches: id, _:id) {
unsafe {
let uiscreen = msg_send![object, screen];
let touches_enum: id = msg_send![touches, objectEnumerator];
let mut touch_events = Vec::new();
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: UITouchPhase = msg_send![touch, phase];
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
};
touch_events.push(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }),
id: touch_id,
location: (location.x as f64, location.y as f64).into(),
phase,
}),
});
}
AppState::handle_nonuser_events(touch_events);
}
}
extern fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) {
unsafe {
let () = msg_send![super(object, class!(UIWindow)), setContentScaleFactor:hidpi_factor];
let view_controller: id = msg_send![object, rootViewController];
let view: id = msg_send![view_controller, view];
let () = msg_send![view, setContentScaleFactor:hidpi_factor];
let bounds: CGRect = msg_send![object, bounds];
let screen: id = msg_send![object, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
};
AppState::handle_nonuser_events(
std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _),
}).chain(std::iter::once(Event::WindowEvent {
window_id: RootWindowId(object.into()),
event: WindowEvent::Resized(size),
}))
);
}
}
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
.expect("Failed to declare class `WinitUIWindow`");
decl.add_method(sel!(becomeKeyWindow),
become_key_window as extern fn(&Object, Sel));
decl.add_method(sel!(resignKeyWindow),
resign_key_window as extern fn(&Object, Sel));
decl.add_method(sel!(touchesBegan:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesMoved:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesEnded:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(touchesCancelled:withEvent:),
handle_touches as extern fn(this: &Object, _: Sel, _: id, _:id));
decl.add_method(sel!(setContentScaleFactor:),
set_content_scale_factor as extern fn(&mut Object, Sel, CGFloat));
CLASS = Some(decl.register());
}
CLASS.unwrap()
}
// requires main thread
pub unsafe fn create_view(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> id {
let class = get_view_class(platform_attributes.root_view_class);
let view: id = msg_send![class, alloc];
assert!(!view.is_null(), "Failed to create `UIView` instance");
let view: id = msg_send![view, initWithFrame:frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
if window_attributes.multitouch {
let () = msg_send![view, setMultipleTouchEnabled:YES];
}
view
}
// requires main thread
pub unsafe fn create_view_controller(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
) -> id {
let class = get_view_controller_class();
let view_controller: id = msg_send![class, alloc];
assert!(!view_controller.is_null(), "Failed to create `UIViewController` instance");
let view_controller: id = msg_send![view_controller, init];
assert!(!view_controller.is_null(), "Failed to initialize `UIViewController` instance");
let status_bar_hidden = if window_attributes.decorations {
NO
} else {
YES
};
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
platform_attributes.valid_orientations,
idiom,
);
let () = msg_send![view_controller, setPrefersStatusBarHidden:status_bar_hidden];
let () = msg_send![view_controller, setSupportedInterfaceOrientations:supported_orientations];
let () = msg_send![view_controller, setView:view];
view_controller
}
// requires main thread
pub unsafe fn create_window(
window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: id,
) -> id {
let class = get_window_class();
let window: id = msg_send![class, alloc];
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
let window: id = msg_send![window, initWithFrame:frame];
assert!(!window.is_null(), "Failed to initialize `UIWindow` instance");
let () = msg_send![window, setRootViewController:view_controller];
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
let () = msg_send![window, setContentScaleFactor:hidpi_factor as CGFloat];
}
if let &Some(ref monitor) = &window_attributes.fullscreen {
let () = msg_send![window, setScreen:monitor.get_uiscreen()];
}
window
}
pub fn create_delegate_class() {
extern fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
unsafe {
AppState::did_finish_launching();
}
YES
}
extern fn did_become_active(_: &Object, _: Sel, _: id) {
unsafe {
AppState::handle_nonuser_event(Event::Suspended(false))
}
}
extern fn will_resign_active(_: &Object, _: Sel, _: id) {
unsafe {
AppState::handle_nonuser_event(Event::Suspended(true))
}
}
extern fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
extern fn did_enter_background(_: &Object, _: Sel, _: id) {}
extern fn will_terminate(_: &Object, _: Sel, _: id) {
unsafe {
let app: id = msg_send![class!(UIApplication), sharedApplication];
let windows: id = msg_send![app, windows];
let windows_enum: id = msg_send![windows, objectEnumerator];
let mut events = Vec::new();
loop {
let window: id = msg_send![windows_enum, nextObject];
if window == nil {
break
}
let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)];
if is_winit_window == YES {
events.push(Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Destroyed,
});
}
}
AppState::handle_nonuser_events(events);
AppState::terminated();
}
}
let ui_responder = class!(UIResponder);
let mut decl = ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
unsafe {
decl.add_method(sel!(application:didFinishLaunchingWithOptions:),
did_finish_launching as extern fn(&mut Object, Sel, id, id) -> BOOL);
decl.add_method(sel!(applicationDidBecomeActive:),
did_become_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillResignActive:),
will_resign_active as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillEnterForeground:),
will_enter_foreground as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationDidEnterBackground:),
did_enter_background as extern fn(&Object, Sel, id));
decl.add_method(sel!(applicationWillTerminate:),
will_terminate as extern fn(&Object, Sel, id));
decl.register();
}
}

View file

@ -0,0 +1,488 @@
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use objc::runtime::{Class, NO, Object, YES};
use dpi::{self, LogicalPosition, LogicalSize};
use icon::Icon;
use monitor::MonitorHandle as RootMonitorHandle;
use platform::ios::{MonitorHandleExtIOS, ValidOrientations};
use window::{
CreationError,
MouseCursor,
WindowAttributes,
};
use platform_impl::{
platform::{
app_state::AppState,
event_loop,
ffi::{
id,
CGFloat,
CGPoint,
CGRect,
CGSize,
UIEdgeInsets,
UIInterfaceOrientationMask,
},
monitor,
view,
EventLoopWindowTarget,
MonitorHandle
},
};
pub struct Inner {
pub window: id,
pub view_controller: id,
pub view: id,
supports_safe_area: bool,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let () = msg_send![self.view, release];
let () = msg_send![self.view_controller, release];
let () = msg_send![self.window, release];
}
}
}
impl Inner {
pub fn set_title(&self, _title: &str) {
debug!("`Window::set_title` is ignored on iOS")
}
pub fn show(&self) {
unsafe {
let () = msg_send![self.window, setHidden:NO];
}
}
pub fn hide(&self) {
unsafe {
let () = msg_send![self.window, setHidden:YES];
}
}
pub fn request_redraw(&self) {
unsafe {
let () = msg_send![self.view, setNeedsDisplay];
}
}
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
unsafe {
let safe_area = self.safe_area_screen_space();
Some(LogicalPosition {
x: safe_area.origin.x,
y: safe_area.origin.y,
})
}
}
pub fn get_position(&self) -> Option<LogicalPosition> {
unsafe {
let screen_frame = self.screen_frame();
Some(LogicalPosition {
x: screen_frame.origin.x,
y: screen_frame.origin.y,
})
}
}
pub fn set_position(&self, position: LogicalPosition) {
unsafe {
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.from_screen_space(new_screen_frame);
let () = msg_send![self.window, setBounds:bounds];
}
}
pub fn get_inner_size(&self) -> Option<LogicalSize> {
unsafe {
let safe_area = self.safe_area_screen_space();
Some(LogicalSize {
width: safe_area.size.width,
height: safe_area.size.height,
})
}
}
pub fn get_outer_size(&self) -> Option<LogicalSize> {
unsafe {
let screen_frame = self.screen_frame();
Some(LogicalSize {
width: screen_frame.size.width,
height: screen_frame.size.height,
})
}
}
pub fn set_inner_size(&self, _size: LogicalSize) {
unimplemented!("not clear what `Window::set_inner_size` means on iOS");
}
pub fn set_min_dimensions(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_min_dimensions` is ignored on iOS")
}
pub fn set_max_dimensions(&self, _dimensions: Option<LogicalSize>) {
warn!("`Window::set_max_dimensions` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
warn!("`Window::set_resizable` is ignored on iOS")
}
pub fn get_hidpi_factor(&self) -> f64 {
unsafe {
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
}
}
pub fn set_cursor(&self, _cursor: MouseCursor) {
debug!("`Window::set_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), String> {
Err("Setting cursor position is not possible on iOS.".to_owned())
}
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
pub fn hide_cursor(&self, _hide: bool) {
debug!("`Window::hide_cursor` is ignored on iOS")
}
pub fn set_maximized(&self, _maximized: bool) {
warn!("`Window::set_maximized` is ignored on iOS")
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
match monitor {
Some(monitor) => {
let uiscreen = monitor.get_uiscreen() as id;
let current: id = msg_send![self.window, screen];
let bounds: CGRect = msg_send![uiscreen, bounds];
// this is pretty slow on iOS, so avoid doing it if we can
if uiscreen != current {
let () = msg_send![self.window, setScreen:uiscreen];
}
let () = msg_send![self.window, setFrame:bounds];
}
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
}
}
}
pub fn get_fullscreen(&self) -> Option<RootMonitorHandle> {
unsafe {
let monitor = self.get_current_monitor();
let uiscreen = monitor.inner.get_uiscreen();
let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(monitor)
} else {
None
}
}
}
pub fn set_decorations(&self, decorations: bool) {
unsafe {
let status_bar_hidden = if decorations { NO } else { YES };
let () = msg_send![self.view_controller, setPrefersStatusBarHidden:status_bar_hidden];
}
}
pub fn set_always_on_top(&self, _always_on_top: bool) {
warn!("`Window::set_always_on_top` is ignored on iOS")
}
pub fn set_window_icon(&self, _icon: Option<Icon>) {
warn!("`Window::set_window_icon` is ignored on iOS")
}
pub fn set_ime_spot(&self, _position: LogicalPosition) {
warn!("`Window::set_ime_spot` is ignored on iOS")
}
pub fn get_current_monitor(&self) -> RootMonitorHandle {
unsafe {
let uiscreen: id = msg_send![self.window, screen];
RootMonitorHandle { inner: MonitorHandle::retained_new(uiscreen) }
}
}
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe {
monitor::uiscreens()
}
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
unsafe {
monitor::main_uiscreen()
}
}
pub fn id(&self) -> WindowId {
self.window.into()
}
}
pub struct Window {
pub inner: Inner,
}
impl Drop for Window {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
unsafe {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
}
&mut self.inner
}
}
impl Window {
pub fn new<T>(
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
if let Some(_) = window_attributes.min_dimensions {
warn!("`WindowAttributes::min_dimensions` is ignored on iOS");
}
if let Some(_) = window_attributes.max_dimensions {
warn!("`WindowAttributes::max_dimensions` is ignored on iOS");
}
if window_attributes.always_on_top {
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
}
// TODO: transparency, visible
unsafe {
let screen = window_attributes.fullscreen
.as_ref()
.map(|screen| screen.get_uiscreen() as _)
.unwrap_or_else(|| monitor::main_uiscreen().get_uiscreen());
let screen_bounds: CGRect = msg_send![screen, bounds];
let frame = match window_attributes.dimensions {
Some(dim) => CGRect {
origin: screen_bounds.origin,
size: CGSize { width: dim.width, height: dim.height },
},
None => screen_bounds,
};
let view = view::create_view(&window_attributes, &platform_attributes, frame.clone());
let view_controller = view::create_view_controller(&window_attributes, &platform_attributes, view);
let window = view::create_window(&window_attributes, &platform_attributes, frame, view_controller);
let supports_safe_area = event_loop.capabilities().supports_safe_area;
let result = Window {
inner: Inner {
window,
view_controller,
view,
supports_safe_area,
},
};
AppState::set_key_window(window);
Ok(result)
}
}
}
// WindowExtIOS
impl Inner {
pub fn get_uiwindow(&self) -> id { self.window }
pub fn get_uiviewcontroller(&self) -> id { self.view_controller }
pub fn get_uiview(&self) -> id { self.view }
pub fn set_hidpi_factor(&self, hidpi_factor: f64) {
unsafe {
assert!(dpi::validate_hidpi_factor(hidpi_factor), "`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor");
let hidpi_factor = hidpi_factor as CGFloat;
let () = msg_send![self.view, setContentScaleFactor:hidpi_factor];
}
}
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
unsafe {
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(valid_orientations, idiom);
msg_send![self.view_controller, setSupportedInterfaceOrientations:supported_orientations];
}
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
self.to_screen_space(msg_send![self.window, bounds])
}
// requires main thread
unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen];
if !screen.is_null() {
let screen_space: id = msg_send![screen, coordinateSpace];
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
} else {
rect
}
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds];
if self.supports_safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
let safe_bounds = CGRect {
origin: CGPoint {
x: bounds.origin.x + safe_area.left,
y: bounds.origin.y + safe_area.top,
},
size: CGSize {
width: bounds.size.width - safe_area.left - safe_area.right,
height: bounds.size.height - safe_area.top - safe_area.bottom,
},
};
self.to_screen_space(safe_bounds)
} else {
let screen_frame = self.to_screen_space(bounds);
let status_bar_frame: CGRect = {
let app: id = msg_send![class!(UIApplication), sharedApplication];
assert!(!app.is_null(), "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS");
msg_send![app, statusBarFrame]
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height)
} else {
let y = status_bar_frame.size.height;
let height = screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y);
(y, height)
};
CGRect {
origin: CGPoint {
x: screen_frame.origin.x,
y,
},
size: CGSize {
width: screen_frame.size.width,
height,
}
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: id,
}
impl WindowId {
pub unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}
}
}
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl From<&Object> for WindowId {
fn from(window: &Object) -> WindowId {
WindowId { window: window as *const _ as _ }
}
}
impl From<&mut Object> for WindowId {
fn from(window: &mut Object) -> WindowId {
WindowId { window: window as _ }
}
}
impl From<id> for WindowId {
fn from(window: id) -> WindowId {
WindowId { window }
}
}
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class,
pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView),
hidpi_factor: None,
valid_orientations: Default::default(),
}
}
}

View file

@ -330,6 +330,10 @@ impl Window {
/// Modifies the title of the window. /// Modifies the title of the window.
/// ///
/// This is a no-op if the window has already been closed. /// This is a no-op if the window has already been closed.
///
/// ## Platform-specific
///
/// - Has no effect on iOS.
#[inline] #[inline]
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
self.window.set_title(title) self.window.set_title(title)
@ -339,8 +343,8 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Has no effect on Android /// - **Android:** Has no effect.
/// /// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn show(&self) { pub fn show(&self) {
self.window.show() self.window.show()
@ -350,8 +354,8 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - Has no effect on Android /// - **Android:** Has no effect.
/// /// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn hide(&self) { pub fn hide(&self) {
self.window.hide() self.window.hide()
@ -368,6 +372,10 @@ impl Window {
/// * While processing `EventsCleared`. /// * While processing `EventsCleared`.
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any /// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
/// directly subsequent `RedrawRequested` event. /// directly subsequent `RedrawRequested` event.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
self.window.request_redraw() self.window.request_redraw()
} }
@ -383,6 +391,11 @@ impl Window {
/// of the visible screen region. /// of the visible screen region.
/// ///
/// Returns `None` if the window no longer exists. /// Returns `None` if the window no longer exists.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// window in the screen space coordinate system.
#[inline] #[inline]
pub fn get_position(&self) -> Option<LogicalPosition> { pub fn get_position(&self) -> Option<LogicalPosition> {
self.window.get_position() self.window.get_position()
@ -392,6 +405,13 @@ impl Window {
/// top-left hand corner of the desktop. /// top-left hand corner of the desktop.
/// ///
/// The same conditions that apply to `get_position` apply to this method. /// The same conditions that apply to `get_position` apply to this method.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// window's [safe area] in the screen space coordinate system.
///
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline] #[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> { pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.window.get_inner_position() self.window.get_inner_position()
@ -402,6 +422,11 @@ impl Window {
/// See `get_position` for more information about the coordinates. /// See `get_position` for more information about the coordinates.
/// ///
/// This is a no-op if the window has already been closed. /// This is a no-op if the window has already been closed.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the
/// window in the screen space coordinate system.
#[inline] #[inline]
pub fn set_position(&self, position: LogicalPosition) { pub fn set_position(&self, position: LogicalPosition) {
self.window.set_position(position) self.window.set_position(position)
@ -414,6 +439,13 @@ impl Window {
/// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be. /// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be.
/// ///
/// Returns `None` if the window no longer exists. /// Returns `None` if the window no longer exists.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window's
/// [safe area] in screen space coordinates.
///
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline] #[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> { pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.window.get_inner_size() self.window.get_inner_size()
@ -425,6 +457,11 @@ impl Window {
/// use `get_inner_size` instead. /// use `get_inner_size` instead.
/// ///
/// Returns `None` if the window no longer exists. /// Returns `None` if the window no longer exists.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window in
/// screen space coordinates.
#[inline] #[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> { pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.window.get_outer_size() self.window.get_outer_size()
@ -435,18 +472,31 @@ impl Window {
/// See `get_inner_size` for more information about the values. /// See `get_inner_size` for more information about the values.
/// ///
/// This is a no-op if the window has already been closed. /// This is a no-op if the window has already been closed.
///
/// ## Platform-specific
///
/// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size`
/// would mean for iOS.
#[inline] #[inline]
pub fn set_inner_size(&self, size: LogicalSize) { pub fn set_inner_size(&self, size: LogicalSize) {
self.window.set_inner_size(size) self.window.set_inner_size(size)
} }
/// Sets a minimum dimension size for the window. /// Sets a minimum dimension size for the window.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) { pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_min_dimensions(dimensions) self.window.set_min_dimensions(dimensions)
} }
/// Sets a maximum dimension size for the window. /// Sets a maximum dimension size for the window.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) { pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
self.window.set_max_dimensions(dimensions) self.window.set_max_dimensions(dimensions)
@ -462,6 +512,10 @@ impl Window {
/// This only has an effect on desktop platforms. /// This only has an effect on desktop platforms.
/// ///
/// Due to a bug in XFCE, this has no effect on Xfwm. /// Due to a bug in XFCE, this has no effect on Xfwm.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_resizable(&self, resizable: bool) { pub fn set_resizable(&self, resizable: bool) {
self.window.set_resizable(resizable) self.window.set_resizable(resizable)
@ -479,19 +533,31 @@ impl Window {
/// ///
/// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable.
/// - **Android:** Always returns 1.0. /// - **Android:** Always returns 1.0.
/// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s
/// [`contentScaleFactor`].
///
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
#[inline] #[inline]
pub fn get_hidpi_factor(&self) -> f64 { pub fn get_hidpi_factor(&self) -> f64 {
self.window.get_hidpi_factor() self.window.get_hidpi_factor()
} }
/// Modifies the mouse cursor of the window. /// Modifies the mouse cursor of the window.
/// Has no effect on Android. ///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
/// - **Android:** Has no effect.
#[inline] #[inline]
pub fn set_cursor(&self, cursor: MouseCursor) { pub fn set_cursor(&self, cursor: MouseCursor) {
self.window.set_cursor(cursor); self.window.set_cursor(cursor);
} }
/// Changes the position of the cursor in window coordinates. /// Changes the position of the cursor in window coordinates.
///
/// ## Platform-specific
///
/// - **iOS:** Always returns an `Err`.
#[inline] #[inline]
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> { pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
self.window.set_cursor_position(position) self.window.set_cursor_position(position)
@ -501,9 +567,10 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward. /// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually
/// /// awkward.
/// This has no effect on Android or iOS. /// - **Android:** Has no effect.
/// - **iOS:** Always returns an Err.
#[inline] #[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
self.window.grab_cursor(grab) self.window.grab_cursor(grab)
@ -513,42 +580,65 @@ impl Window {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// On Windows and X11, the cursor is only hidden within the confines of the window. /// - **Windows:** The cursor is only hidden within the confines of the window.
/// /// - **X11:** The cursor is only hidden within the confines of the window.
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
/// window. /// outside of the window.
/// /// - **iOS:** Has no effect.
/// This has no effect on Android or iOS. /// - **Android:** Has no effect.
#[inline] #[inline]
pub fn hide_cursor(&self, hide: bool) { pub fn hide_cursor(&self, hide: bool) {
self.window.hide_cursor(hide) self.window.hide_cursor(hide)
} }
/// Sets the window to maximized or back /// Sets the window to maximized or back.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_maximized(&self, maximized: bool) { pub fn set_maximized(&self, maximized: bool) {
self.window.set_maximized(maximized) self.window.set_maximized(maximized)
} }
/// Sets the window to fullscreen or back /// Sets the window to fullscreen or back.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) { pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
self.window.set_fullscreen(monitor) self.window.set_fullscreen(monitor)
} }
/// Gets the window's current fullscreen state. /// Gets the window's current fullscreen state.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn get_fullscreen(&self) -> Option<MonitorHandle> { pub fn get_fullscreen(&self) -> Option<MonitorHandle> {
self.window.get_fullscreen() self.window.get_fullscreen()
} }
/// Turn window decorations on or off. /// Turn window decorations on or off.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden
/// via [`setPrefersStatusBarHidden`].
///
/// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc
#[inline] #[inline]
pub fn set_decorations(&self, decorations: bool) { pub fn set_decorations(&self, decorations: bool) {
self.window.set_decorations(decorations) self.window.set_decorations(decorations)
} }
/// Change whether or not the window will always be on top of other windows. /// Change whether or not the window will always be on top of other windows.
///
/// ## Platform-specific
///
/// - **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_always_on_top(&self, always_on_top: bool) { pub fn set_always_on_top(&self, always_on_top: bool) {
self.window.set_always_on_top(always_on_top) self.window.set_always_on_top(always_on_top)
@ -568,12 +658,20 @@ impl Window {
} }
/// Sets location of IME candidate box in client area coordinates relative to the top left. /// Sets location of IME candidate box in client area coordinates relative to the top left.
///
/// ## Platform-specific
///
/// **iOS:** Has no effect.
#[inline] #[inline]
pub fn set_ime_spot(&self, position: LogicalPosition) { pub fn set_ime_spot(&self, position: LogicalPosition) {
self.window.set_ime_spot(position) self.window.set_ime_spot(position)
} }
/// Returns the monitor on which the window currently resides /// Returns the monitor on which the window currently resides
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn get_current_monitor(&self) -> MonitorHandle { pub fn get_current_monitor(&self) -> MonitorHandle {
self.window.get_current_monitor() self.window.get_current_monitor()
@ -582,6 +680,10 @@ impl Window {
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
/// ///
/// This is the same as `EventLoop::get_available_monitors`, and is provided for convenience. /// This is the same as `EventLoop::get_available_monitors`, and is provided for convenience.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn get_available_monitors(&self) -> AvailableMonitorsIter { pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
let data = self.window.get_available_monitors(); let data = self.window.get_available_monitors();
@ -591,6 +693,10 @@ impl Window {
/// Returns the primary monitor of the system. /// Returns the primary monitor of the system.
/// ///
/// This is the same as `EventLoop::get_primary_monitor`, and is provided for convenience. /// This is the same as `EventLoop::get_primary_monitor`, and is provided for convenience.
///
/// ## Platform-specific
///
/// **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn get_primary_monitor(&self) -> MonitorHandle { pub fn get_primary_monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.window.get_primary_monitor() } MonitorHandle { inner: self.window.get_primary_monitor() }