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<()> {
|
||||
/// 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<()> {
|
||||
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`.
|
||||
/// 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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn new_user_event() -> EventLoop<T> {
|
||||
EventLoop {
|
||||
event_loop: platform_impl::EventLoop::new(),
|
||||
|
|
|
@ -2,7 +2,21 @@
|
|||
|
||||
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.
|
||||
pub trait WindowExtIOS {
|
||||
|
@ -11,10 +25,25 @@ pub trait WindowExtIOS {
|
|||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
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.
|
||||
///
|
||||
/// The pointer will become invalid when the `Window` is destroyed.
|
||||
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 {
|
||||
|
@ -23,10 +52,25 @@ impl WindowExtIOS for Window {
|
|||
self.window.get_uiwindow() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uiviewcontroller(&self) -> *mut c_void {
|
||||
self.window.get_uiviewcontroller() as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_uiview(&self) -> *mut c_void {
|
||||
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.
|
||||
|
@ -35,6 +79,15 @@ pub trait WindowBuilderExtIOS {
|
|||
///
|
||||
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]`
|
||||
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 {
|
||||
|
@ -43,6 +96,18 @@ impl WindowBuilderExtIOS for WindowBuilder {
|
|||
self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) };
|
||||
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.
|
||||
|
@ -57,3 +122,40 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
|||
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)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::ops::BitOr;
|
||||
use std::os::raw::*;
|
||||
|
||||
use objc::{Encode, Encoding};
|
||||
use objc::runtime::Object;
|
||||
|
||||
use platform::ios::{Idiom, ValidOrientations};
|
||||
|
||||
pub type id = *mut Object;
|
||||
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")]
|
||||
pub type CGFloat = f32;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
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)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGPoint {
|
||||
|
@ -26,13 +35,6 @@ pub struct CGPoint {
|
|||
pub y: CGFloat,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGRect {
|
||||
pub origin: CGPoint,
|
||||
pub size: CGSize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CGSize {
|
||||
|
@ -40,13 +42,134 @@ pub struct CGSize {
|
|||
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 = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "GlKit", kind = "framework")]
|
||||
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(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
|
@ -54,31 +177,115 @@ extern {
|
|||
delegateClassName: id,
|
||||
) -> c_int;
|
||||
|
||||
// SInt32 CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );
|
||||
pub fn CFRunLoopRunInMode(
|
||||
mode: CFStringRef,
|
||||
seconds: CFTimeInterval,
|
||||
returnAfterSourceHandled: Boolean,
|
||||
) -> i32;
|
||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
||||
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
|
||||
|
||||
pub fn CFRunLoopObserverCreate(
|
||||
allocator: CFAllocatorRef,
|
||||
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 fn setjmp(env: *mut c_void) -> c_int;
|
||||
pub fn longjmp(env: *mut c_void, val: c_int) -> !;
|
||||
pub type Boolean = u8;
|
||||
pub enum CFAllocator {}
|
||||
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 {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSString), alloc]
|
||||
|
|
|
@ -47,663 +47,55 @@
|
|||
//!
|
||||
//! This is how those event are represented in winit:
|
||||
//!
|
||||
//! - applicationDidBecomeActive is Focused(true)
|
||||
//! - applicationWillResignActive is Focused(false)
|
||||
//! - applicationDidEnterBackground is Suspended(true)
|
||||
//! - applicationWillEnterForeground is Suspended(false)
|
||||
//! - applicationWillTerminate is Destroyed
|
||||
//! - applicationDidBecomeActive is Suspended(false)
|
||||
//! - applicationWillResignActive is Suspended(true)
|
||||
//! - applicationWillTerminate is LoopDestroyed
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! 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")]
|
||||
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::os::raw::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc::declare::ClassDecl;
|
||||
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;
|
||||
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
|
||||
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
|
||||
// window size/position.
|
||||
macro_rules! assert_main_thread {
|
||||
($($t:tt)*) => {
|
||||
if !msg_send![class!(NSThread), isMainThread] {
|
||||
panic!($($t)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
use self::ffi::{
|
||||
CFTimeInterval,
|
||||
CFRunLoopRunInMode,
|
||||
CGFloat,
|
||||
CGPoint,
|
||||
CGRect,
|
||||
id,
|
||||
JBLEN,
|
||||
JmpBuf,
|
||||
kCFRunLoopDefaultMode,
|
||||
kCFRunLoopRunHandledSource,
|
||||
longjmp,
|
||||
nil,
|
||||
NSString,
|
||||
setjmp,
|
||||
UIApplicationMain,
|
||||
};
|
||||
mod monitor;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
static mut JMPBUF: Option<Box<JmpBuf>> = None;
|
||||
|
||||
pub struct Window {
|
||||
_events_queue: Arc<RefCell<VecDeque<Event>>>,
|
||||
delegate_state: Box<DelegateState>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DelegateState {
|
||||
window: id,
|
||||
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!()
|
||||
}
|
||||
}
|
||||
pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use self::monitor::MonitorHandle;
|
||||
pub use self::window::{
|
||||
PlatformSpecificWindowBuilderAttributes,
|
||||
Window,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct WindowId;
|
||||
|
||||
impl WindowId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
WindowId
|
||||
}
|
||||
pub struct DeviceId {
|
||||
uiscreen: ffi::id,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub unsafe fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||
pub root_view_class: &'static Class,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
fn default() -> Self {
|
||||
PlatformSpecificWindowBuilderAttributes {
|
||||
root_view_class: class!(UIView),
|
||||
DeviceId {
|
||||
uiscreen: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: AFAIK transparency is enabled by default on iOS,
|
||||
// so to be consistent with other platforms we have to change that.
|
||||
impl Window {
|
||||
pub fn new(
|
||||
ev: &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);
|
||||
unsafe impl Send for DeviceId {}
|
||||
unsafe impl Sync for 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.
|
||||
///
|
||||
/// This is a no-op if the window has already been closed.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Has no effect on iOS.
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.window.set_title(title)
|
||||
|
@ -339,8 +343,8 @@ impl Window {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Has no effect on Android
|
||||
///
|
||||
/// - **Android:** Has no effect.
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn show(&self) {
|
||||
self.window.show()
|
||||
|
@ -350,8 +354,8 @@ impl Window {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Has no effect on Android
|
||||
///
|
||||
/// - **Android:** Has no effect.
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn hide(&self) {
|
||||
self.window.hide()
|
||||
|
@ -368,6 +372,10 @@ impl Window {
|
|||
/// * While processing `EventsCleared`.
|
||||
/// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any
|
||||
/// directly subsequent `RedrawRequested` event.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
pub fn request_redraw(&self) {
|
||||
self.window.request_redraw()
|
||||
}
|
||||
|
@ -383,6 +391,11 @@ impl Window {
|
|||
/// of the visible screen region.
|
||||
///
|
||||
/// 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]
|
||||
pub fn get_position(&self) -> Option<LogicalPosition> {
|
||||
self.window.get_position()
|
||||
|
@ -392,6 +405,13 @@ impl Window {
|
|||
/// top-left hand corner of the desktop.
|
||||
///
|
||||
/// 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]
|
||||
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
|
||||
self.window.get_inner_position()
|
||||
|
@ -402,6 +422,11 @@ impl Window {
|
|||
/// See `get_position` for more information about the coordinates.
|
||||
///
|
||||
/// 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]
|
||||
pub fn set_position(&self, position: LogicalPosition) {
|
||||
self.window.set_position(position)
|
||||
|
@ -414,6 +439,13 @@ impl Window {
|
|||
/// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be.
|
||||
///
|
||||
/// 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]
|
||||
pub fn get_inner_size(&self) -> Option<LogicalSize> {
|
||||
self.window.get_inner_size()
|
||||
|
@ -425,6 +457,11 @@ impl Window {
|
|||
/// use `get_inner_size` instead.
|
||||
///
|
||||
/// 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]
|
||||
pub fn get_outer_size(&self) -> Option<LogicalSize> {
|
||||
self.window.get_outer_size()
|
||||
|
@ -435,18 +472,31 @@ impl Window {
|
|||
/// See `get_inner_size` for more information about the values.
|
||||
///
|
||||
/// 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]
|
||||
pub fn set_inner_size(&self, size: LogicalSize) {
|
||||
self.window.set_inner_size(size)
|
||||
}
|
||||
|
||||
/// Sets a minimum dimension size for the window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_min_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.window.set_min_dimensions(dimensions)
|
||||
}
|
||||
|
||||
/// Sets a maximum dimension size for the window.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_max_dimensions(&self, dimensions: Option<LogicalSize>) {
|
||||
self.window.set_max_dimensions(dimensions)
|
||||
|
@ -462,6 +512,10 @@ impl Window {
|
|||
/// This only has an effect on desktop platforms.
|
||||
///
|
||||
/// Due to a bug in XFCE, this has no effect on Xfwm.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
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.
|
||||
/// - **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]
|
||||
pub fn get_hidpi_factor(&self) -> f64 {
|
||||
self.window.get_hidpi_factor()
|
||||
}
|
||||
|
||||
/// Modifies the mouse cursor of the window.
|
||||
/// Has no effect on Android.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Has no effect.
|
||||
/// - **Android:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_cursor(&self, cursor: MouseCursor) {
|
||||
self.window.set_cursor(cursor);
|
||||
}
|
||||
|
||||
/// Changes the position of the cursor in window coordinates.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Always returns an `Err`.
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), String> {
|
||||
self.window.set_cursor_position(position)
|
||||
|
@ -501,9 +567,10 @@ impl Window {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
|
||||
///
|
||||
/// This has no effect on Android or iOS.
|
||||
/// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually
|
||||
/// awkward.
|
||||
/// - **Android:** Has no effect.
|
||||
/// - **iOS:** Always returns an Err.
|
||||
#[inline]
|
||||
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
|
||||
self.window.grab_cursor(grab)
|
||||
|
@ -513,42 +580,65 @@ impl Window {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// On Windows and 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
|
||||
/// window.
|
||||
///
|
||||
/// This has no effect on Android or iOS.
|
||||
/// - **Windows:** The cursor is only hidden within the confines of the window.
|
||||
/// - **X11:** The cursor is only hidden within the confines of the window.
|
||||
/// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is
|
||||
/// outside of the window.
|
||||
/// - **iOS:** Has no effect.
|
||||
/// - **Android:** Has no effect.
|
||||
#[inline]
|
||||
pub fn hide_cursor(&self, hide: bool) {
|
||||
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]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
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]
|
||||
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
|
||||
self.window.set_fullscreen(monitor)
|
||||
}
|
||||
|
||||
/// Gets the window's current fullscreen state.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn get_fullscreen(&self) -> Option<MonitorHandle> {
|
||||
self.window.get_fullscreen()
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
self.window.set_decorations(decorations)
|
||||
}
|
||||
|
||||
/// Change whether or not the window will always be on top of other windows.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
||||
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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Has no effect.
|
||||
#[inline]
|
||||
pub fn set_ime_spot(&self, position: LogicalPosition) {
|
||||
self.window.set_ime_spot(position)
|
||||
}
|
||||
|
||||
/// Returns the monitor on which the window currently resides
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> MonitorHandle {
|
||||
self.window.get_current_monitor()
|
||||
|
@ -582,6 +680,10 @@ impl Window {
|
|||
/// 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.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// **iOS:** Can only be called on the main thread.
|
||||
#[inline]
|
||||
pub fn get_available_monitors(&self) -> AvailableMonitorsIter {
|
||||
let data = self.window.get_available_monitors();
|
||||
|
@ -591,6 +693,10 @@ impl Window {
|
|||
/// Returns the primary monitor of the system.
|
||||
///
|
||||
/// 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]
|
||||
pub fn get_primary_monitor(&self) -> MonitorHandle {
|
||||
MonitorHandle { inner: self.window.get_primary_monitor() }
|
||||
|
|
Loading…
Reference in a new issue