mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
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:
parent
e5aa906b01
commit
3a7350cbb9
|
@ -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(),
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
580
src/platform_impl/ios/app_state.rs
Normal file
580
src/platform_impl/ios/app_state.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
334
src/platform_impl/ios/event_loop.rs
Normal file
334
src/platform_impl/ios/event_loop.rs
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
|
||||||
|
|
171
src/platform_impl/ios/monitor.rs
Normal file
171
src/platform_impl/ios/monitor.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
395
src/platform_impl/ios/view.rs
Normal file
395
src/platform_impl/ios/view.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
488
src/platform_impl/ios/window.rs
Normal file
488
src/platform_impl/ios/window.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
src/window.rs
138
src/window.rs
|
@ -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() }
|
||||||
|
|
Loading…
Reference in a new issue