Improve macOS/iOS/Web thread safety

Co-authored-by: daxpedda <daxpedda@gmail.com>
This commit is contained in:
Mads Marquart 2023-08-14 21:19:57 +02:00 committed by GitHub
parent 119462795a
commit af6c343d0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 552 additions and 724 deletions

View file

@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # Unreleased
- Make iOS windows usable from other threads.
- Reexport `raw-window-handle` in `window` module. - Reexport `raw-window-handle` in `window` module.
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables. - **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>` - **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`

View file

@ -78,11 +78,11 @@ objc2 = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3" core-graphics = "0.22.3"
dispatch = "0.2.0"
[target.'cfg(target_os = "macos")'.dependencies.icrate] [target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4" version = "0.0.4"
features = [ features = [
"dispatch",
"Foundation", "Foundation",
"Foundation_NSArray", "Foundation_NSArray",
"Foundation_NSAttributedString", "Foundation_NSAttributedString",
@ -98,6 +98,7 @@ features = [
[target.'cfg(target_os = "ios")'.dependencies.icrate] [target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4" version = "0.0.4"
features = [ features = [
"dispatch",
"Foundation", "Foundation",
"Foundation_NSArray", "Foundation_NSArray",
"Foundation_NSString", "Foundation_NSString",

View file

@ -78,28 +78,33 @@ pub trait WindowExtIOS {
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
#[inline] #[inline]
fn set_scale_factor(&self, scale_factor: f64) { fn set_scale_factor(&self, scale_factor: f64) {
self.window.set_scale_factor(scale_factor) self.window
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
} }
#[inline] #[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations) self.window
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
} }
#[inline] #[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) { fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden) self.window
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
} }
#[inline] #[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window self.window.maybe_queue_on_main(move |w| {
.set_preferred_screen_edges_deferring_system_gestures(edges) w.set_preferred_screen_edges_deferring_system_gestures(edges)
})
} }
#[inline] #[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) { fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window.set_prefers_status_bar_hidden(hidden) self.window
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
} }
} }

View file

@ -84,72 +84,78 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for Window { impl WindowExtMacOS for Window {
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen() self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
} }
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen) self.window
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
fn has_shadow(&self) -> bool { fn has_shadow(&self) -> bool {
self.window.has_shadow() self.window.maybe_wait_on_main(|w| w.has_shadow())
} }
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow) self.window
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
} }
#[inline] #[inline]
fn set_tabbing_identifier(&self, identifier: &str) { fn set_tabbing_identifier(&self, identifier: &str) {
self.window.set_tabbing_identifier(identifier); self.window
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
} }
#[inline] #[inline]
fn tabbing_identifier(&self) -> String { fn tabbing_identifier(&self) -> String {
self.window.tabbing_identifier() self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
} }
#[inline] #[inline]
fn select_next_tab(&self) { fn select_next_tab(&self) {
self.window.select_next_tab(); self.window.maybe_queue_on_main(|w| w.select_next_tab())
} }
#[inline] #[inline]
fn select_previous_tab(&self) { fn select_previous_tab(&self) {
self.window.select_previous_tab(); self.window.maybe_queue_on_main(|w| w.select_previous_tab())
} }
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { fn select_tab_at_index(&self, index: usize) {
self.window.select_tab_at_index(index); self.window
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
} }
#[inline] #[inline]
fn num_tabs(&self) -> usize { fn num_tabs(&self) -> usize {
self.window.num_tabs() self.window.maybe_wait_on_main(|w| w.num_tabs())
} }
#[inline] #[inline]
fn is_document_edited(&self) -> bool { fn is_document_edited(&self) -> bool {
self.window.is_document_edited() self.window.maybe_wait_on_main(|w| w.is_document_edited())
} }
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { fn set_document_edited(&self, edited: bool) {
self.window.set_document_edited(edited) self.window
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.set_option_as_alt(option_as_alt) self.window
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
} }
#[inline] #[inline]
fn option_as_alt(&self) -> OptionAsAlt { fn option_as_alt(&self) -> OptionAsAlt {
self.window.option_as_alt() self.window.maybe_wait_on_main(|w| w.option_as_alt())
} }
} }

View file

@ -31,7 +31,7 @@ use crate::event::Event;
use crate::event_loop::ControlFlow; use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop; use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget; use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder; use crate::window::{Window, WindowBuilder};
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
@ -40,6 +40,13 @@ pub trait WindowExtWebSys {
fn canvas(&self) -> Option<HtmlCanvasElement>; fn canvas(&self) -> Option<HtmlCanvasElement>;
} }
impl WindowExtWebSys for Window {
#[inline]
fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas()
}
}
pub trait WindowBuilderExtWebSys { pub trait WindowBuilderExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`](crate::window::Window). If
/// [`None`], [`WindowBuilder::build()`] will create one. /// [`None`], [`WindowBuilder::build()`] will create one.

View file

@ -797,6 +797,14 @@ impl Window {
}) })
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId WindowId
} }

View file

@ -1,11 +1,8 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::{ use std::collections::VecDeque;
collections::VecDeque,
ops::{Deref, DerefMut},
};
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker}; use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
@ -31,9 +28,9 @@ use crate::{
}; };
pub struct Inner { pub struct Inner {
pub(crate) window: Id<WinitUIWindow>, window: Id<WinitUIWindow>,
pub(crate) view_controller: Id<WinitViewController>, view_controller: Id<WinitViewController>,
pub(crate) view: Id<WinitView>, view: Id<WinitView>,
gl_or_metal_backed: bool, gl_or_metal_backed: bool,
} }
@ -76,68 +73,58 @@ impl Inner {
pub fn pre_present_notify(&self) {} pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe { let safe_area = self.safe_area_screen_space();
let safe_area = self.safe_area_screen_space(); let position = LogicalPosition {
let position = LogicalPosition { x: safe_area.origin.x as f64,
x: safe_area.origin.x as f64, y: safe_area.origin.y as f64,
y: safe_area.origin.y as f64, };
}; let scale_factor = self.scale_factor();
let scale_factor = self.scale_factor(); Ok(position.to_physical(scale_factor))
Ok(position.to_physical(scale_factor))
}
} }
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe { let screen_frame = self.screen_frame();
let screen_frame = self.screen_frame(); let position = LogicalPosition {
let position = LogicalPosition { x: screen_frame.origin.x as f64,
x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64,
y: screen_frame.origin.y as f64, };
}; let scale_factor = self.scale_factor();
let scale_factor = self.scale_factor(); Ok(position.to_physical(scale_factor))
Ok(position.to_physical(scale_factor))
}
} }
pub fn set_outer_position(&self, physical_position: Position) { pub fn set_outer_position(&self, physical_position: Position) {
unsafe { let scale_factor = self.scale_factor();
let scale_factor = self.scale_factor(); let position = physical_position.to_logical::<f64>(scale_factor);
let position = physical_position.to_logical::<f64>(scale_factor); let screen_frame = self.screen_frame();
let screen_frame = self.screen_frame(); let new_screen_frame = CGRect {
let new_screen_frame = CGRect { origin: CGPoint {
origin: CGPoint { x: position.x as _,
x: position.x as _, y: position.y as _,
y: position.y as _, },
}, size: screen_frame.size,
size: screen_frame.size, };
}; let bounds = self.rect_from_screen_space(new_screen_frame);
let bounds = self.rect_from_screen_space(new_screen_frame); self.window.setBounds(bounds);
self.window.setBounds(bounds);
}
} }
pub fn inner_size(&self) -> PhysicalSize<u32> { pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe { let scale_factor = self.scale_factor();
let scale_factor = self.scale_factor(); let safe_area = self.safe_area_screen_space();
let safe_area = self.safe_area_screen_space(); let size = LogicalSize {
let size = LogicalSize { width: safe_area.size.width as f64,
width: safe_area.size.width as f64, height: safe_area.size.height as f64,
height: safe_area.size.height as f64, };
}; size.to_physical(scale_factor)
size.to_physical(scale_factor)
}
} }
pub fn outer_size(&self) -> PhysicalSize<u32> { pub fn outer_size(&self) -> PhysicalSize<u32> {
unsafe { let scale_factor = self.scale_factor();
let scale_factor = self.scale_factor(); let screen_frame = self.screen_frame();
let screen_frame = self.screen_frame(); let size = LogicalSize {
let size = LogicalSize { width: screen_frame.size.width as f64,
width: screen_frame.size.width as f64, height: screen_frame.size.height as f64,
height: screen_frame.size.height as f64, };
}; size.to_physical(scale_factor)
size.to_physical(scale_factor)
}
} }
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> { pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
@ -262,22 +249,20 @@ impl Inner {
} }
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
unsafe { let monitor = self.current_monitor_inner();
let monitor = self.current_monitor_inner(); let uiscreen = monitor.ui_screen();
let uiscreen = monitor.ui_screen(); let screen_space_bounds = self.screen_frame();
let screen_space_bounds = self.screen_frame(); let screen_bounds = uiscreen.bounds();
let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons // TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y && screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height && screen_space_bounds.size.height == screen_bounds.size.height
{ {
Some(Fullscreen::Borderless(Some(monitor))) Some(Fullscreen::Borderless(Some(monitor)))
} else { } else {
None None
}
} }
} }
@ -375,32 +360,7 @@ impl Inner {
} }
pub struct Window { pub struct Window {
pub inner: Inner, inner: MainThreadBound<Inner>,
}
impl Drop for Window {
fn drop(&mut self) {
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 {
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 {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&mut self.inner
}
} }
impl Window { impl Window {
@ -497,15 +457,25 @@ impl Window {
} }
} }
let inner = Inner {
window,
view_controller,
view,
gl_or_metal_backed,
};
Ok(Window { Ok(Window {
inner: Inner { inner: MainThreadBound::new(inner, mtm),
window,
view_controller,
view,
gl_or_metal_backed,
},
}) })
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner, _mtm| f(inner))
}
} }
// WindowExtIOS // WindowExtIOS
@ -542,27 +512,23 @@ impl Inner {
} }
impl Inner { impl Inner {
// requires main thread fn screen_frame(&self) -> CGRect {
unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds()) self.rect_to_screen_space(self.window.bounds())
} }
// requires main thread fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace(); let screen_space = self.window.screen().coordinateSpace();
self.window self.window
.convertRect_toCoordinateSpace(rect, &screen_space) .convertRect_toCoordinateSpace(rect, &screen_space)
} }
// requires main thread fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace(); let screen_space = self.window.screen().coordinateSpace();
self.window self.window
.convertRect_fromCoordinateSpace(rect, &screen_space) .convertRect_fromCoordinateSpace(rect, &screen_space)
} }
// requires main thread fn safe_area_screen_space(&self) -> CGRect {
unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds = self.window.bounds(); let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area { if app_state::os_capabilities().safe_area {
let safe_area = self.window.safeAreaInsets(); let safe_area = self.window.safeAreaInsets();
@ -580,7 +546,7 @@ impl Inner {
} else { } else {
let screen_frame = self.rect_to_screen_space(bounds); let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = { let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect( let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS", "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
); );
app.statusBarFrame() app.statusBarFrame()

View file

@ -306,6 +306,14 @@ impl Window {
} }
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
match self { match self {

View file

@ -4,10 +4,12 @@ use core_foundation::{
base::CFRelease, base::CFRelease,
data::{CFDataGetBytePtr, CFDataRef}, data::{CFDataGetBytePtr, CFDataRef},
}; };
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id; use objc2::rc::Id;
use smol_str::SmolStr; use smol_str::SmolStr;
use super::appkit::{NSEvent, NSEventModifierFlags}; use super::appkit::{NSEvent, NSEventModifierFlags};
use super::util::Never;
use super::window::WinitWindow; use super::window::WinitWindow;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
@ -16,10 +18,7 @@ use crate::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode, Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
}, },
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
platform_impl::platform::{ platform_impl::platform::ffi,
ffi,
util::{get_kbd_type, Never},
},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -75,7 +74,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
} }
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
} }
let keyboard_type = get_kbd_type(); let keyboard_type = MainThreadMarker::run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0; let mut result_len = 0;
let mut dead_keys = 0; let mut dead_keys = 0;

View file

@ -17,12 +17,12 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use icrate::Foundation::is_main_thread; use icrate::Foundation::MainThreadMarker;
use objc2::rc::{autoreleasepool, Id}; use objc2::rc::{autoreleasepool, Id};
use objc2::{msg_send_id, ClassType}; use objc2::{msg_send_id, ClassType};
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent, NSWindow}; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow};
use crate::{ use crate::{
error::EventLoopError, error::EventLoopError,
event::Event, event::Event,
@ -66,15 +66,8 @@ impl PanicInfo {
} }
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
pub sender: mpsc::Sender<T>, // this is only here to be cloned elsewhere
pub receiver: mpsc::Receiver<T>, pub receiver: mpsc::Receiver<T>,
} mtm: MainThreadMarker,
impl<T> Default for EventLoopWindowTarget<T> {
fn default() -> Self {
let (sender, receiver) = mpsc::channel();
EventLoopWindowTarget { sender, receiver }
}
} }
impl<T: 'static> EventLoopWindowTarget<T> { impl<T: 'static> EventLoopWindowTarget<T> {
@ -97,11 +90,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
impl<T> EventLoopWindowTarget<T> { impl<T> EventLoopWindowTarget<T> {
pub(crate) fn hide_application(&self) { pub(crate) fn hide_application(&self) {
NSApp().hide(None) NSApplication::shared(self.mtm).hide(None)
} }
pub(crate) fn hide_other_applications(&self) { pub(crate) fn hide_other_applications(&self) {
NSApp().hideOtherApplications(None) NSApplication::shared(self.mtm).hideOtherApplications(None)
} }
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) { pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
@ -118,8 +111,10 @@ pub struct EventLoop<T: 'static> {
/// it around here as well. /// it around here as well.
_delegate: Id<ApplicationDelegate>, _delegate: Id<ApplicationDelegate>,
sender: mpsc::Sender<T>,
window_target: Rc<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>, panic_info: Rc<PanicInfo>,
mtm: MainThreadMarker,
/// We make sure that the callback closure is dropped during a panic /// We make sure that the callback closure is dropped during a panic
/// by making the event loop own it. /// by making the event loop own it.
@ -151,9 +146,8 @@ impl<T> EventLoop<T> {
pub(crate) fn new( pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes, attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> { ) -> Result<Self, EventLoopError> {
if !is_main_thread() { let mtm = MainThreadMarker::new()
panic!("On macOS, `EventLoop` must be created on the main thread!"); .expect("On macOS, `EventLoop` must be created on the main thread!");
}
// This must be done before `NSApp()` (equivalent to sending // This must be done before `NSApp()` (equivalent to sending
// `sharedApplication`) is called anywhere else, or we'll end up // `sharedApplication`) is called anywhere else, or we'll end up
@ -180,12 +174,16 @@ impl<T> EventLoop<T> {
let panic_info: Rc<PanicInfo> = Default::default(); let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info)); setup_control_flow_observers(Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop { Ok(EventLoop {
_delegate: delegate, _delegate: delegate,
sender,
window_target: Rc::new(RootWindowTarget { window_target: Rc::new(RootWindowTarget {
p: Default::default(), p: EventLoopWindowTarget { receiver, mtm },
_marker: PhantomData, _marker: PhantomData,
}), }),
mtm,
panic_info, panic_info,
_callback: None, _callback: None,
}) })
@ -233,7 +231,7 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback)); self._callback = Some(Rc::clone(&callback));
let exit_code = autoreleasepool(|_| { let exit_code = autoreleasepool(|_| {
let app = NSApp(); let app = NSApplication::shared(self.mtm);
// A bit of juggling with the callback references to make sure // A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback. // that `self.callback` is the only owner of the callback.
@ -408,7 +406,7 @@ impl<T> EventLoop<T> {
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender.clone()) EventLoopProxy::new(self.sender.clone())
} }
} }

View file

@ -17,10 +17,8 @@ mod view;
mod window; mod window;
mod window_delegate; mod window_delegate;
use std::{fmt, ops::Deref}; use std::fmt;
use self::window::WinitWindow;
use self::window_delegate::WinitWindowDelegate;
pub(crate) use self::{ pub(crate) use self::{
event::KeyEventExtra, event::KeyEventExtra,
event_loop::{ event_loop::{
@ -29,11 +27,9 @@ pub(crate) use self::{
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, WindowId},
}; };
use crate::{ use crate::event::DeviceId as RootDeviceId;
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
};
use objc2::rc::{autoreleasepool, Id};
pub(crate) use self::window::Window;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;
@ -49,47 +45,12 @@ impl DeviceId {
// Constant device ID; to be removed when if backend is updated to report real device IDs. // Constant device ID; to be removed when if backend is updated to report real device IDs.
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
pub(crate) struct Window {
pub(crate) window: Id<WinitWindow>,
// We keep this around so that it doesn't get dropped until the window does.
_delegate: Id<WinitWindowDelegate>,
}
impl Drop for Window {
fn drop(&mut self) {
// Ensure the window is closed
util::close_sync(&self.window);
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum OsError { pub enum OsError {
CGError(core_graphics::base::CGError), CGError(core_graphics::base::CGError),
CreationError(&'static str), CreationError(&'static str),
} }
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = WinitWindow;
#[inline]
fn deref(&self) -> &Self::Target {
&self.window
}
}
impl Window {
pub(crate) fn new<T: 'static>(
_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
Ok(Window { window, _delegate })
}
}
impl fmt::Display for OsError { impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {

View file

@ -1,9 +1,3 @@
#![allow(clippy::unnecessary_cast)]
mod r#async;
pub(crate) use self::r#async::*;
use core_graphics::display::CGDisplay; use core_graphics::display::CGDisplay;
use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger};
@ -50,6 +44,7 @@ impl Drop for TraceGuard {
// For consistency with other platforms, this will... // For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner // 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one // 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
#[allow(clippy::unnecessary_cast)]
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64 CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64
} }

View file

@ -1,218 +0,0 @@
use std::ops::Deref;
use dispatch::Queue;
use icrate::Foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString};
use objc2::rc::autoreleasepool;
use crate::{
dpi::{LogicalPosition, LogicalSize},
platform_impl::platform::{
appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask},
ffi,
window::WinitWindow,
},
};
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
// This should *only* be used to dispatch to the main queue.
// While it is indeed not guaranteed that these types can safely be sent to
// other threads, we know that they're safe to use on the main thread.
struct MainThreadSafe<T>(T);
unsafe impl<T> Send for MainThreadSafe<T> {}
impl<T> Deref for MainThreadSafe<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn run_on_main<R: Send>(f: impl FnOnce() -> R + Send) -> R {
if is_main_thread() {
f()
} else {
Queue::main().exec_sync(f)
}
}
fn set_style_mask(window: &NSWindow, mask: NSWindowStyleMask) {
window.setStyleMask(mask);
// If we don't do this, key handling will break
// (at least until the window is clicked again/etc.)
let _ = window.makeFirstResponder(Some(&window.contentView()));
}
// Always use this function instead of trying to modify `styleMask` directly!
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
// Otherwise, this would vomit out errors about not being on the main thread
// and fail to do anything.
pub(crate) fn set_style_mask_sync(window: &NSWindow, mask: NSWindowStyleMask) {
let window = MainThreadSafe(window);
run_on_main(move || {
set_style_mask(&window, mask);
})
}
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
// and just fails silently. Anyway, GCD to the rescue!
pub(crate) fn set_content_size_sync(window: &NSWindow, size: LogicalSize<f64>) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat));
});
}
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
// to log errors.
pub(crate) fn set_frame_top_left_point_sync(window: &NSWindow, point: NSPoint) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.setFrameTopLeftPoint(point);
});
}
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
pub(crate) fn set_level_sync(window: &NSWindow, level: NSWindowLevel) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.setLevel(level);
});
}
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
pub(crate) fn set_ignore_mouse_events_sync(window: &NSWindow, ignore: bool) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.setIgnoresMouseEvents(ignore);
});
}
// `toggleFullScreen` is thread-safe, but our additional logic to account for
// window styles isn't.
pub(crate) fn toggle_full_screen_sync(window: &WinitWindow, not_fullscreen: bool) {
let window = MainThreadSafe(window);
run_on_main(move || {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
if not_fullscreen {
let curr_mask = window.styleMask();
let required =
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
set_style_mask(&window, required);
window
.lock_shared_state("toggle_full_screen_sync")
.saved_style = Some(curr_mask);
}
}
// Window level must be restored from `CGShieldingWindowLevel()
// + 1` back to normal in order for `toggleFullScreen` to do
// anything
window.setLevel(NSWindowLevel::Normal);
window.toggleFullScreen(None);
});
}
pub(crate) unsafe fn restore_display_mode_sync(ns_screen: u32) {
run_on_main(move || {
unsafe { ffi::CGRestorePermanentDisplayConfiguration() };
assert_eq!(
unsafe { ffi::CGDisplayRelease(ns_screen) },
ffi::kCGErrorSuccess
);
});
}
// `setMaximized` is not thread-safe
pub(crate) fn set_maximized_sync(window: &WinitWindow, is_zoomed: bool, maximized: bool) {
let window = MainThreadSafe(window);
run_on_main(move || {
let mut shared_state = window.lock_shared_state("set_maximized_sync");
// Save the standard frame sized if it is not zoomed
if !is_zoomed {
shared_state.standard_frame = Some(window.frame());
}
shared_state.maximized = maximized;
if shared_state.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
}
if window
.styleMask()
.contains(NSWindowStyleMask::NSResizableWindowMask)
{
drop(shared_state);
// Just use the native zoom if resizable
window.zoom(None);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if maximized {
let screen = NSScreen::main().expect("no screen found");
screen.visibleFrame()
} else {
shared_state.saved_standard_frame()
};
drop(shared_state);
window.setFrame_display(new_rect, false);
}
});
}
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
// but with an odd delay.
pub(crate) fn order_out_sync(window: &NSWindow) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.orderOut(None);
});
}
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
// actually works, but with an odd delay.
pub(crate) fn make_key_and_order_front_sync(window: &NSWindow) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.makeKeyAndOrderFront(None);
});
}
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
// window drag regions, which throws an exception when not done in the main
// thread
pub(crate) fn set_title_sync(window: &NSWindow, title: &str) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.setTitle(&NSString::from_str(title));
});
}
// `close:` is thread-safe, but we want the event to be triggered from the main
// thread. Though, it's a good idea to look into that more...
pub(crate) fn close_sync(window: &NSWindow) {
let window = MainThreadSafe(window);
run_on_main(move || {
autoreleasepool(move |_| {
window.close();
});
});
}
pub(crate) fn set_ime_cursor_area_sync(
window: &WinitWindow,
logical_spot: LogicalPosition<f64>,
size: LogicalSize<f64>,
) {
let window = MainThreadSafe(window);
run_on_main(move || {
window.view().set_ime_cursor_area(logical_spot, size);
});
}
pub(crate) fn get_kbd_type() -> u8 {
run_on_main(|| unsafe { ffi::LMGetKbdType() })
}

View file

@ -22,6 +22,7 @@ use crate::{
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
appkit::NSWindowOrderingMode, appkit::NSWindowOrderingMode,
event_loop::EventLoopWindowTarget,
ffi, ffi,
monitor::{self, MonitorHandle, VideoMode}, monitor::{self, MonitorHandle, VideoMode},
util, util,
@ -36,8 +37,8 @@ use crate::{
}; };
use core_graphics::display::{CGDisplay, CGPoint}; use core_graphics::display::{CGDisplay, CGPoint};
use icrate::Foundation::{ use icrate::Foundation::{
is_main_thread, CGFloat, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize, CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint,
NSString, NSRect, NSSize, NSString,
}; };
use objc2::declare::{Ivar, IvarDrop}; use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id}; use objc2::rc::{autoreleasepool, Id};
@ -50,6 +51,47 @@ use super::appkit::{
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowTabbingMode, NSWindowTitleVisibility,
}; };
pub(crate) struct Window {
window: MainThreadBound<Id<WinitWindow>>,
// We keep this around so that it doesn't get dropped until the window does.
_delegate: MainThreadBound<Id<WinitWindowDelegate>>,
}
impl Drop for Window {
fn drop(&mut self) {
self.window
.get_on_main(|window, _| autoreleasepool(|_| window.close()))
}
}
impl Window {
pub(crate) fn new<T: 'static>(
_window_target: &EventLoopWindowTarget<T>,
attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<Self, RootOsError> {
let mtm = MainThreadMarker::new()
.expect("windows can only be created on the main thread on macOS");
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
Ok(Window {
window: MainThreadBound::new(window, mtm),
_delegate: MainThreadBound::new(_delegate, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WinitWindow) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WinitWindow) -> R + Send,
) -> R {
self.window.get_on_main(|window, _mtm| f(window))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub usize); pub struct WindowId(pub usize);
@ -250,16 +292,12 @@ impl Drop for SharedStateMutexGuard<'_> {
impl WinitWindow { impl WinitWindow {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(crate) fn new( fn new(
attrs: WindowAttributes, attrs: WindowAttributes,
pl_attrs: PlatformSpecificWindowBuilderAttributes, pl_attrs: PlatformSpecificWindowBuilderAttributes,
) -> Result<(Id<Self>, Id<WinitWindowDelegate>), RootOsError> { ) -> Result<(Id<Self>, Id<WinitWindowDelegate>), RootOsError> {
trace_scope!("WinitWindow::new"); trace_scope!("WinitWindow::new");
if !is_main_thread() {
panic!("Windows can only be created on the main thread on macOS");
}
let this = autoreleasepool(|_| { let this = autoreleasepool(|_| {
let screen = match attrs.fullscreen.clone().map(Into::into) { let screen = match attrs.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Borderless(Some(monitor))) Some(Fullscreen::Borderless(Some(monitor)))
@ -537,16 +575,21 @@ impl WinitWindow {
SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn) SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn)
} }
fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { fn set_style_mask(&self, mask: NSWindowStyleMask) {
util::set_style_mask_sync(self, mask); self.setStyleMask(mask);
// If we don't do this, key handling will break
// (at least until the window is clicked again/etc.)
let _ = self.makeFirstResponder(Some(&self.contentView()));
} }
}
impl WinitWindow {
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId(self as *const Self as usize) WindowId(self as *const Self as usize)
} }
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
util::set_title_sync(self, title); self.setTitle(&NSString::from_str(title))
} }
pub fn set_transparent(&self, transparent: bool) { pub fn set_transparent(&self, transparent: bool) {
@ -555,8 +598,8 @@ impl WinitWindow {
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
match visible { match visible {
true => util::make_key_and_order_front_sync(self), true => self.makeKeyAndOrderFront(None),
false => util::order_out_sync(self), false => self.orderOut(None),
} }
} }
@ -595,7 +638,7 @@ impl WinitWindow {
pub fn set_outer_position(&self, position: Position) { pub fn set_outer_position(&self, position: Position) {
let scale_factor = self.scale_factor(); let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor); let position = position.to_logical(scale_factor);
util::set_frame_top_left_point_sync(self, util::window_position(position)); self.setFrameTopLeftPoint(util::window_position(position));
} }
#[inline] #[inline]
@ -617,7 +660,8 @@ impl WinitWindow {
#[inline] #[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> { pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let scale_factor = self.scale_factor(); let scale_factor = self.scale_factor();
util::set_content_size_sync(self, size.to_logical(scale_factor)); let size: LogicalSize<f64> = size.to_logical(scale_factor);
self.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat));
None None
} }
@ -725,7 +769,7 @@ impl WinitWindow {
} else { } else {
mask &= !NSWindowStyleMask::NSResizableWindowMask; mask &= !NSWindowStyleMask::NSResizableWindowMask;
} }
self.set_style_mask_sync(mask); self.set_style_mask(mask);
} }
// Otherwise, we don't change the mask until we exit fullscreen. // Otherwise, we don't change the mask until we exit fullscreen.
} }
@ -753,7 +797,7 @@ impl WinitWindow {
// This must happen before the button's "enabled" status has been set, // This must happen before the button's "enabled" status has been set,
// hence we do it synchronously. // hence we do it synchronously.
self.set_style_mask_sync(mask); self.set_style_mask(mask);
// We edit the button directly instead of using `NSResizableWindowMask`, // We edit the button directly instead of using `NSResizableWindowMask`,
// since that mask also affect the resizability of the window (which is // since that mask also affect the resizability of the window (which is
@ -849,7 +893,7 @@ impl WinitWindow {
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
util::set_ignore_mouse_events_sync(self, !hittest); self.setIgnoresMouseEvents(!hittest);
Ok(()) Ok(())
} }
@ -862,14 +906,14 @@ impl WinitWindow {
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
let needs_temp_mask = !curr_mask.contains(required); let needs_temp_mask = !curr_mask.contains(required);
if needs_temp_mask { if needs_temp_mask {
self.set_style_mask_sync(required); self.set_style_mask(required);
} }
let is_zoomed = self.isZoomed(); let is_zoomed = self.isZoomed();
// Roll back temp styles // Roll back temp styles
if needs_temp_mask { if needs_temp_mask {
self.set_style_mask_sync(curr_mask); self.set_style_mask(curr_mask);
} }
is_zoomed is_zoomed
@ -900,7 +944,7 @@ impl WinitWindow {
drop(shared_state_lock); drop(shared_state_lock);
self.set_style_mask_sync(mask); self.set_style_mask(mask);
self.set_maximized(maximized); self.set_maximized(maximized);
} }
@ -929,7 +973,38 @@ impl WinitWindow {
if is_zoomed == maximized { if is_zoomed == maximized {
return; return;
}; };
util::set_maximized_sync(self, is_zoomed, maximized);
let mut shared_state = self.lock_shared_state("set_maximized");
// Save the standard frame sized if it is not zoomed
if !is_zoomed {
shared_state.standard_frame = Some(self.frame());
}
shared_state.maximized = maximized;
if shared_state.fullscreen.is_some() {
// Handle it in window_did_exit_fullscreen
return;
}
if self
.styleMask()
.contains(NSWindowStyleMask::NSResizableWindowMask)
{
drop(shared_state);
// Just use the native zoom if resizable
self.zoom(None);
} else {
// if it's not resizable, we set the frame directly
let new_rect = if maximized {
let screen = NSScreen::main().expect("no screen found");
screen.visibleFrame()
} else {
shared_state.saved_standard_frame()
};
drop(shared_state);
self.setFrame_display(new_rect, false);
}
} }
#[inline] #[inline]
@ -985,7 +1060,7 @@ impl WinitWindow {
// The coordinate system here has its origin at bottom-left // The coordinate system here has its origin at bottom-left
// and Y goes up // and Y goes up
screen_frame.origin.y += screen_frame.size.height; screen_frame.origin.y += screen_frame.size.height;
util::set_frame_top_left_point_sync(self, screen_frame.origin); self.setFrameTopLeftPoint(screen_frame.origin);
} }
} }
@ -1061,22 +1136,43 @@ impl WinitWindow {
self.lock_shared_state("set_fullscreen").fullscreen = fullscreen.clone(); self.lock_shared_state("set_fullscreen").fullscreen = fullscreen.clone();
match (&old_fullscreen, &fullscreen) { fn toggle_fullscreen(window: &WinitWindow) {
(&None, &Some(_)) => { // Window level must be restored from `CGShieldingWindowLevel()
util::toggle_full_screen_sync(self, old_fullscreen.is_none()); // + 1` back to normal in order for `toggleFullScreen` to do
// anything
window.setLevel(NSWindowLevel::Normal);
window.toggleFullScreen(None);
}
match (old_fullscreen, fullscreen) {
(None, Some(_)) => {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
let curr_mask = self.styleMask();
let required = NSWindowStyleMask::NSTitledWindowMask
| NSWindowStyleMask::NSResizableWindowMask;
if !curr_mask.contains(required) {
self.set_style_mask(required);
self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask);
}
toggle_fullscreen(self);
} }
(&Some(Fullscreen::Borderless(_)), &None) => { (Some(Fullscreen::Borderless(_)), None) => {
// State is restored by `window_did_exit_fullscreen` // State is restored by `window_did_exit_fullscreen`
util::toggle_full_screen_sync(self, old_fullscreen.is_none()); toggle_fullscreen(self);
} }
(&Some(Fullscreen::Exclusive(ref video_mode)), &None) => { (Some(Fullscreen::Exclusive(ref video_mode)), None) => {
unsafe { unsafe {
util::restore_display_mode_sync(video_mode.monitor().native_identifier()) ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
}; };
// Rest of the state is restored by `window_did_exit_fullscreen` toggle_fullscreen(self);
util::toggle_full_screen_sync(self, old_fullscreen.is_none());
} }
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => { (Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
// If we're already in fullscreen mode, calling // If we're already in fullscreen mode, calling
// `CGDisplayCapture` will place the shielding window on top of // `CGDisplayCapture` will place the shielding window on top of
// our window, which results in a black display and is not what // our window, which results in a black display and is not what
@ -1099,7 +1195,7 @@ impl WinitWindow {
NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1); NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1);
self.setLevel(window_level); self.setLevel(window_level);
} }
(&Some(Fullscreen::Exclusive(ref video_mode)), &Some(Fullscreen::Borderless(_))) => { (Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
let presentation_options = self let presentation_options = self
.lock_shared_state("set_fullscreen") .lock_shared_state("set_fullscreen")
.save_presentation_opts .save_presentation_opts
@ -1111,7 +1207,11 @@ impl WinitWindow {
NSApp().setPresentationOptions(presentation_options); NSApp().setPresentationOptions(presentation_options);
unsafe { unsafe {
util::restore_display_mode_sync(video_mode.monitor().native_identifier()) ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
}; };
// Restore the normal window level following the Borderless fullscreen // Restore the normal window level following the Borderless fullscreen
@ -1156,7 +1256,7 @@ impl WinitWindow {
} }
new_mask new_mask
}; };
self.set_style_mask_sync(new_mask); self.set_style_mask(new_mask);
} }
#[inline] #[inline]
@ -1171,7 +1271,7 @@ impl WinitWindow {
WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL, WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL,
WindowLevel::Normal => NSWindowLevel::Normal, WindowLevel::Normal => NSWindowLevel::Normal,
}; };
util::set_level_sync(self, level); self.setLevel(level);
} }
#[inline] #[inline]
@ -1191,7 +1291,7 @@ impl WinitWindow {
let scale_factor = self.scale_factor(); let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor); let logical_spot = spot.to_logical(scale_factor);
let size = size.to_logical(scale_factor); let size = size.to_logical(scale_factor);
util::set_ime_cursor_area_sync(self, logical_spot, size); self.view().set_ime_cursor_area(logical_spot, size);
} }
#[inline] #[inline]
@ -1209,7 +1309,7 @@ impl WinitWindow {
if !is_minimized && is_visible { if !is_minimized && is_visible {
NSApp().activateIgnoringOtherApps(true); NSApp().activateIgnoringOtherApps(true);
util::make_key_and_order_front_sync(self); self.makeKeyAndOrderFront(None);
} }
} }
@ -1263,9 +1363,9 @@ impl WinitWindow {
fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) { fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) {
let current_style_mask = self.styleMask(); let current_style_mask = self.styleMask();
if on { if on {
util::set_style_mask_sync(self, current_style_mask | mask); self.set_style_mask(current_style_mask | mask);
} else { } else {
util::set_style_mask_sync(self, current_style_mask & (!mask)); self.set_style_mask(current_style_mask & (!mask));
} }
} }
@ -1360,7 +1460,7 @@ impl WindowExtMacOS for WinitWindow {
true true
} else { } else {
let new_mask = self.saved_style(&mut shared_state_lock); let new_mask = self.saved_style(&mut shared_state_lock);
self.set_style_mask_sync(new_mask); self.set_style_mask(new_mask);
shared_state_lock.is_simple_fullscreen = false; shared_state_lock.is_simple_fullscreen = false;
let save_presentation_opts = shared_state_lock.save_presentation_opts; let save_presentation_opts = shared_state_lock.save_presentation_opts;

View file

@ -126,6 +126,14 @@ impl Window {
}) })
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId { WindowId {

View file

@ -96,16 +96,6 @@ impl<const SYNC: bool, T, E> MainThreadSafe<SYNC, T, E> {
} }
}) })
} }
fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
Self::MAIN_THREAD.with(|is_main_thread| {
if *is_main_thread.deref() {
Some(f(self.value.write().unwrap().as_mut().unwrap()))
} else {
None
}
})
}
} }
impl<const SYNC: bool, T, E> Clone for MainThreadSafe<SYNC, T, E> { impl<const SYNC: bool, T, E> Clone for MainThreadSafe<SYNC, T, E> {
@ -219,17 +209,15 @@ impl<T> AsyncReceiver<T> {
pub struct Dispatcher<T: 'static>(MainThreadSafe<true, T, Closure<T>>); pub struct Dispatcher<T: 'static>(MainThreadSafe<true, T, Closure<T>>);
pub enum Closure<T> { pub struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
Ref(Box<dyn FnOnce(&T) + Send>),
RefMut(Box<dyn FnOnce(&mut T) + Send>),
}
impl<T> Dispatcher<T> { impl<T> Dispatcher<T> {
#[track_caller] #[track_caller]
pub fn new(value: T) -> Option<Self> { pub fn new(value: T) -> Option<Self> {
MainThreadSafe::new(value, |value, closure| match closure { MainThreadSafe::new(value, |value, Closure(closure)| {
Closure::Ref(f) => f(value.read().unwrap().as_ref().unwrap()), // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
Closure::RefMut(f) => f(value.write().unwrap().as_mut().unwrap()), // funny with it here. See `Self::queue()`.
closure(value.read().unwrap().as_ref().unwrap())
}) })
.map(Self) .map(Self)
} }
@ -238,30 +226,26 @@ impl<T> Dispatcher<T> {
if self.is_main_thread() { if self.is_main_thread() {
self.0.with(f).unwrap() self.0.with(f).unwrap()
} else { } else {
self.0.send(Closure::Ref(Box::new(f))) self.0.send(Closure(Box::new(f)))
} }
} }
pub fn dispatch_mut(&self, f: impl 'static + FnOnce(&mut T) + Send) { pub fn queue<R: Send>(&self, f: impl FnOnce(&T) -> R + Send) -> R {
if self.is_main_thread() {
self.0.with_mut(f).unwrap()
} else {
self.0.send(Closure::RefMut(Box::new(f)))
}
}
pub fn queue<R: 'static + Send>(&self, f: impl 'static + FnOnce(&T) -> R + Send) -> R {
if self.is_main_thread() { if self.is_main_thread() {
self.0.with(f).unwrap() self.0.with(f).unwrap()
} else { } else {
let pair = Arc::new((Mutex::new(None), Condvar::new())); let pair = Arc::new((Mutex::new(None), Condvar::new()));
let closure = Closure::Ref(Box::new({ let closure = Box::new({
let pair = pair.clone(); let pair = pair.clone();
move |value| { move |value: &T| {
*pair.0.lock().unwrap() = Some(f(value)); *pair.0.lock().unwrap() = Some(f(value));
pair.1.notify_one(); pair.1.notify_one();
} }
})); }) as Box<dyn FnOnce(&T) + Send>;
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
// safe because this function won't return until `f` has finished executing. See
// `Self::new()`.
let closure = Closure(unsafe { std::mem::transmute(closure) });
self.0.send(closure); self.0.send(closure);

View file

@ -16,8 +16,6 @@ pub use self::resize_scaling::ResizeScaleHandle;
pub use self::timeout::{IdleCallback, Timeout}; pub use self::timeout::{IdleCallback, Timeout};
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::dpi::{LogicalPosition, LogicalSize};
use crate::platform::web::WindowExtWebSys;
use crate::window::Window;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use web_sys::{ use web_sys::{
CssStyleDeclaration, Document, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState, CssStyleDeclaration, Document, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
@ -52,12 +50,6 @@ pub fn on_page_transition(
} }
} }
impl WindowExtWebSys for Window {
fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas()
}
}
pub fn scale_factor(window: &web_sys::Window) -> f64 { pub fn scale_factor(window: &web_sys::Window) -> f64 {
window.device_pixel_ratio() window.device_pixel_ratio()
} }

View file

@ -13,24 +13,23 @@ use super::r#async::Dispatcher;
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::vec_deque::IntoIter as VecDequeIter;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::rc::Rc; use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
pub struct Window { pub struct Window {
id: WindowId, inner: Dispatcher<Inner>,
has_focus: Arc<AtomicBool>,
pub inner: Dispatcher<Inner>,
} }
pub struct Inner { pub struct Inner {
id: WindowId,
pub window: web_sys::Window, pub window: web_sys::Window,
document: Document, document: Document,
canvas: Rc<RefCell<backend::Canvas>>, canvas: Rc<RefCell<backend::Canvas>>,
previous_pointer: RefCell<&'static str>, previous_pointer: RefCell<&'static str>,
destroy_fn: Option<Box<dyn FnOnce()>>, destroy_fn: Option<Box<dyn FnOnce()>>,
has_focus: Arc<AtomicBool>,
} }
impl Window { impl Window {
@ -55,41 +54,42 @@ impl Window {
let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id)));
let has_focus = canvas.borrow().has_focus.clone(); let has_focus = canvas.borrow().has_focus.clone();
let window = Window { let inner = Inner {
id, id,
window: window.clone(),
document: document.clone(),
canvas,
previous_pointer: RefCell::new("auto"),
destroy_fn: Some(destroy_fn),
has_focus, has_focus,
inner: Dispatcher::new(Inner {
window: window.clone(),
document: document.clone(),
canvas,
previous_pointer: RefCell::new("auto"),
destroy_fn: Some(destroy_fn),
})
.unwrap(),
}; };
window.set_title(&attr.title); inner.set_title(&attr.title);
window.set_maximized(attr.maximized); inner.set_maximized(attr.maximized);
window.set_visible(attr.visible); inner.set_visible(attr.visible);
window.set_window_icon(attr.window_icon); inner.set_window_icon(attr.window_icon);
Ok(window) Ok(Window {
inner: Dispatcher::new(inner).unwrap(),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
self.inner.dispatch(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.queue(f)
} }
pub fn canvas(&self) -> Option<HtmlCanvasElement> { pub fn canvas(&self) -> Option<HtmlCanvasElement> {
self.inner.with(|inner| inner.canvas.borrow().raw().clone()) self.inner.with(|inner| inner.canvas.borrow().raw().clone())
} }
}
impl Inner {
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
if self self.canvas.borrow().set_attribute("alt", title)
.inner
.with(|inner| inner.canvas.borrow().set_attribute("alt", title))
.is_none()
{
let title = title.to_owned();
self.inner
.dispatch(move |inner| inner.canvas.borrow().set_attribute("alt", &title));
}
} }
pub fn set_transparent(&self, _transparent: bool) {} pub fn set_transparent(&self, _transparent: bool) {}
@ -104,21 +104,17 @@ impl Window {
} }
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
self.inner.dispatch(move |inner| { self.canvas.borrow().request_animation_frame();
inner.canvas.borrow().request_animation_frame();
});
} }
pub fn pre_present_notify(&self) {} pub fn pre_present_notify(&self) {}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
self.inner.queue(|inner| { Ok(self
Ok(inner .canvas
.canvas .borrow()
.borrow() .position()
.position() .to_physical(self.scale_factor()))
.to_physical(inner.scale_factor()))
})
} }
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
@ -127,17 +123,15 @@ impl Window {
} }
pub fn set_outer_position(&self, position: Position) { pub fn set_outer_position(&self, position: Position) {
self.inner.dispatch(move |inner| { let canvas = self.canvas.borrow();
let canvas = inner.canvas.borrow(); let position = position.to_logical::<f64>(self.scale_factor());
let position = position.to_logical::<f64>(inner.scale_factor());
backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position)
});
} }
#[inline] #[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> { pub fn inner_size(&self) -> PhysicalSize<u32> {
self.inner.queue(|inner| inner.canvas.borrow().inner_size()) self.canvas.borrow().inner_size()
} }
#[inline] #[inline]
@ -148,43 +142,24 @@ impl Window {
#[inline] #[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> { pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
self.inner.dispatch(move |inner| { let size = size.to_logical(self.scale_factor());
let size = size.to_logical(inner.scale_factor()); let canvas = self.canvas.borrow();
let canvas = inner.canvas.borrow(); backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size);
backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size);
});
None None
} }
#[inline] #[inline]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) { pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
self.inner.dispatch(move |inner| { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor()));
let dimensions = let canvas = self.canvas.borrow();
dimensions.map(|dimensions| dimensions.to_logical(inner.scale_factor())); backend::set_canvas_min_size(canvas.document(), canvas.raw(), canvas.style(), dimensions)
let canvas = inner.canvas.borrow();
backend::set_canvas_min_size(
canvas.document(),
canvas.raw(),
canvas.style(),
dimensions,
)
})
} }
#[inline] #[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) { pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
self.inner.dispatch(move |inner| { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor()));
let dimensions = let canvas = self.canvas.borrow();
dimensions.map(|dimensions| dimensions.to_logical(inner.scale_factor())); backend::set_canvas_max_size(canvas.document(), canvas.raw(), canvas.style(), dimensions)
let canvas = inner.canvas.borrow();
backend::set_canvas_max_size(
canvas.document(),
canvas.raw(),
canvas.style(),
dimensions,
)
})
} }
#[inline] #[inline]
@ -216,19 +191,13 @@ impl Window {
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.inner.queue(|inner| inner.scale_factor()) super::backend::scale_factor(&self.window)
} }
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.inner.dispatch(move |inner| { *self.previous_pointer.borrow_mut() = cursor.name();
*inner.previous_pointer.borrow_mut() = cursor.name(); backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name());
backend::set_canvas_style_property(
inner.canvas.borrow().raw(),
"cursor",
cursor.name(),
);
});
} }
#[inline] #[inline]
@ -238,36 +207,31 @@ impl Window {
#[inline] #[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.inner.queue(move |inner| { let lock = match mode {
let lock = match mode { CursorGrabMode::None => false,
CursorGrabMode::None => false, CursorGrabMode::Locked => true,
CursorGrabMode::Locked => true, CursorGrabMode::Confined => {
CursorGrabMode::Confined => { return Err(ExternalError::NotSupported(NotSupportedError::new()))
return Err(ExternalError::NotSupported(NotSupportedError::new())) }
} };
};
inner self.canvas
.canvas .borrow()
.borrow() .set_cursor_lock(lock)
.set_cursor_lock(lock) .map_err(ExternalError::Os)
.map_err(ExternalError::Os)
})
} }
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
self.inner.dispatch(move |inner| { if !visible {
if !visible { backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none");
backend::set_canvas_style_property(inner.canvas.borrow().raw(), "cursor", "none"); } else {
} else { backend::set_canvas_style_property(
backend::set_canvas_style_property( self.canvas.borrow().raw(),
inner.canvas.borrow().raw(), "cursor",
"cursor", &self.previous_pointer.borrow(),
&inner.previous_pointer.borrow(), );
); }
}
});
} }
#[inline] #[inline]
@ -309,24 +273,20 @@ impl Window {
#[inline] #[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
self.inner.queue(|inner| { if self.canvas.borrow().is_fullscreen() {
if inner.canvas.borrow().is_fullscreen() { Some(Fullscreen::Borderless(Some(MonitorHandle)))
Some(Fullscreen::Borderless(Some(MonitorHandle))) } else {
} else { None
None }
}
})
} }
#[inline] #[inline]
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.inner.dispatch(move |inner| { if fullscreen.is_some() {
if fullscreen.is_some() { self.canvas.borrow().request_fullscreen();
inner.canvas.borrow().request_fullscreen(); } else if self.canvas.borrow().is_fullscreen() {
} else if inner.canvas.borrow().is_fullscreen() { backend::exit_fullscreen(&self.document);
backend::exit_fullscreen(&inner.document); }
}
});
} }
#[inline] #[inline]
@ -365,9 +325,7 @@ impl Window {
#[inline] #[inline]
pub fn focus_window(&self) { pub fn focus_window(&self) {
self.inner.dispatch(|inner| { let _ = self.canvas.borrow().raw().focus();
let _ = inner.canvas.borrow().raw().focus();
})
} }
#[inline] #[inline]
@ -381,8 +339,8 @@ impl Window {
} }
#[inline] #[inline]
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
VecDeque::new().into_iter() VecDeque::new()
} }
#[inline] #[inline]
@ -412,14 +370,12 @@ impl Window {
#[inline] #[inline]
pub fn theme(&self) -> Option<Theme> { pub fn theme(&self) -> Option<Theme> {
self.inner.queue(|inner| { backend::is_dark_mode(&self.window).map(|is_dark_mode| {
backend::is_dark_mode(&inner.window).map(|is_dark_mode| { if is_dark_mode {
if is_dark_mode { Theme::Dark
Theme::Dark } else {
} else { Theme::Light
Theme::Light }
}
})
}) })
} }
@ -437,23 +393,13 @@ impl Window {
} }
} }
impl Drop for Window { impl Drop for Inner {
fn drop(&mut self) { fn drop(&mut self) {
self.inner.dispatch_mut(|inner| { if let Some(destroy_fn) = self.destroy_fn.take() {
if let Some(destroy_fn) = inner.destroy_fn.take() { destroy_fn();
destroy_fn(); }
}
});
} }
} }
impl Inner {
#[inline]
pub fn scale_factor(&self) -> f64 {
super::backend::scale_factor(&self.window)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub(crate) u32); pub struct WindowId(pub(crate) u32);

View file

@ -104,6 +104,16 @@ impl Window {
unsafe { init(w_attr, pl_attr, event_loop) } unsafe { init(w_attr, pl_attr, event_loop) }
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
// TODO: Use `thread_executor` here
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
// TODO: Use `thread_executor` here
f(self)
}
fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { fn window_state_lock(&self) -> MutexGuard<'_, WindowState> {
self.window_state.lock().unwrap() self.window_state.lock().unwrap()
} }

View file

@ -23,6 +23,18 @@ pub use raw_window_handle;
/// Represents a window. /// Represents a window.
/// ///
///
/// # Threading
///
/// This is `Send + Sync`, meaning that it can be freely used from other
/// threads.
///
/// However, some platforms (macOS, Web and iOS) only allow user interface
/// interactions on the main thread, so on those platforms, if you use the
/// window from a thread other than the main, the code is scheduled to run on
/// the main thread, and your thread may be blocked until that completes.
///
///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
@ -59,13 +71,15 @@ impl fmt::Debug for Window {
impl Drop for Window { impl Drop for Window {
fn drop(&mut self) { fn drop(&mut self) {
// If the window is in exclusive fullscreen, we must restore the desktop self.window.maybe_wait_on_main(|w| {
// video mode (generally this would be done on application exit, but // If the window is in exclusive fullscreen, we must restore the desktop
// closing the window doesn't necessarily always mean application exit, // video mode (generally this would be done on application exit, but
// such as when there are multiple windows) // closing the window doesn't necessarily always mean application exit,
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() { // such as when there are multiple windows)
self.set_fullscreen(None); if let Some(Fullscreen::Exclusive(_)) = w.fullscreen().map(|f| f.into()) {
} w.set_fullscreen(None);
}
})
} }
} }
@ -468,12 +482,10 @@ impl WindowBuilder {
self, self,
window_target: &EventLoopWindowTarget<T>, window_target: &EventLoopWindowTarget<T>,
) -> Result<Window, OsError> { ) -> Result<Window, OsError> {
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific).map( let window =
|window| { platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)?;
window.request_redraw(); window.maybe_queue_on_main(|w| w.request_redraw());
Window { window } Ok(Window { window })
},
)
} }
} }
@ -501,7 +513,7 @@ impl Window {
/// Returns an identifier unique to the window. /// Returns an identifier unique to the window.
#[inline] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId(self.window.id()) self.window.maybe_wait_on_main(|w| WindowId(w.id()))
} }
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
@ -524,7 +536,7 @@ impl Window {
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.window.scale_factor() self.window.maybe_wait_on_main(|w| w.scale_factor())
} }
/// Queues a [`Event::RedrawRequested`] event to be emitted that aligns with the windowing /// Queues a [`Event::RedrawRequested`] event to be emitted that aligns with the windowing
@ -555,7 +567,7 @@ impl Window {
/// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested
#[inline] #[inline]
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
self.window.request_redraw() self.window.maybe_queue_on_main(|w| w.request_redraw())
} }
/// Notify the windowing system that you're before presenting to the window. /// Notify the windowing system that you're before presenting to the window.
@ -592,7 +604,7 @@ impl Window {
/// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested /// [`Event::RedrawRequested`]: crate::event::Event::RedrawRequested
#[inline] #[inline]
pub fn pre_present_notify(&self) { pub fn pre_present_notify(&self) {
self.window.pre_present_notify(); self.window.maybe_queue_on_main(|w| w.pre_present_notify());
} }
/// Reset the dead key state of the keyboard. /// Reset the dead key state of the keyboard.
@ -608,7 +620,7 @@ impl Window {
// at least, then this function should be provided through a platform specific // at least, then this function should be provided through a platform specific
// extension trait // extension trait
pub fn reset_dead_keys(&self) { pub fn reset_dead_keys(&self) {
self.window.reset_dead_keys(); self.window.maybe_queue_on_main(|w| w.reset_dead_keys())
} }
} }
@ -630,7 +642,7 @@ impl Window {
/// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc
#[inline] #[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
self.window.inner_position() self.window.maybe_wait_on_main(|w| w.inner_position())
} }
/// Returns the position of the top-left hand corner of the window relative to the /// Returns the position of the top-left hand corner of the window relative to the
@ -651,7 +663,7 @@ impl Window {
/// - **Android / Wayland:** Always returns [`NotSupportedError`]. /// - **Android / Wayland:** Always returns [`NotSupportedError`].
#[inline] #[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
self.window.outer_position() self.window.maybe_wait_on_main(|w| w.outer_position())
} }
/// Modifies the position of the window. /// Modifies the position of the window.
@ -683,7 +695,9 @@ impl Window {
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
#[inline] #[inline]
pub fn set_outer_position<P: Into<Position>>(&self, position: P) { pub fn set_outer_position<P: Into<Position>>(&self, position: P) {
self.window.set_outer_position(position.into()) let position = position.into();
self.window
.maybe_queue_on_main(move |w| w.set_outer_position(position))
} }
/// Returns the physical size of the window's client area. /// Returns the physical size of the window's client area.
@ -700,7 +714,7 @@ impl Window {
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
#[inline] #[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> { pub fn inner_size(&self) -> PhysicalSize<u32> {
self.window.inner_size() self.window.maybe_wait_on_main(|w| w.inner_size())
} }
/// Request the new size for the window. /// Request the new size for the window.
@ -741,7 +755,9 @@ impl Window {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn request_inner_size<S: Into<Size>>(&self, size: S) -> Option<PhysicalSize<u32>> { pub fn request_inner_size<S: Into<Size>>(&self, size: S) -> Option<PhysicalSize<u32>> {
self.window.request_inner_size(size.into()) let size = size.into();
self.window
.maybe_wait_on_main(|w| w.request_inner_size(size))
} }
/// Returns the physical size of the entire window. /// Returns the physical size of the entire window.
@ -757,7 +773,7 @@ impl Window {
/// [`Window::inner_size`]._ /// [`Window::inner_size`]._
#[inline] #[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> { pub fn outer_size(&self) -> PhysicalSize<u32> {
self.window.outer_size() self.window.maybe_wait_on_main(|w| w.outer_size())
} }
/// Sets a minimum dimension size for the window. /// Sets a minimum dimension size for the window.
@ -780,7 +796,9 @@ impl Window {
/// - **iOS / Android / Orbital:** Unsupported. /// - **iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_min_inner_size<S: Into<Size>>(&self, min_size: Option<S>) { pub fn set_min_inner_size<S: Into<Size>>(&self, min_size: Option<S>) {
self.window.set_min_inner_size(min_size.map(|s| s.into())) let min_size = min_size.map(|s| s.into());
self.window
.maybe_queue_on_main(move |w| w.set_min_inner_size(min_size))
} }
/// Sets a maximum dimension size for the window. /// Sets a maximum dimension size for the window.
@ -803,7 +821,9 @@ impl Window {
/// - **iOS / Android / Orbital:** Unsupported. /// - **iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_max_inner_size<S: Into<Size>>(&self, max_size: Option<S>) { pub fn set_max_inner_size<S: Into<Size>>(&self, max_size: Option<S>) {
self.window.set_max_inner_size(max_size.map(|s| s.into())) let max_size = max_size.map(|s| s.into());
self.window
.maybe_queue_on_main(move |w| w.set_max_inner_size(max_size))
} }
/// Returns window resize increments if any were set. /// Returns window resize increments if any were set.
@ -813,7 +833,7 @@ impl Window {
/// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`]. /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`].
#[inline] #[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> { pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
self.window.resize_increments() self.window.maybe_wait_on_main(|w| w.resize_increments())
} }
/// Sets window resize increments. /// Sets window resize increments.
@ -828,8 +848,9 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_resize_increments<S: Into<Size>>(&self, increments: Option<S>) { pub fn set_resize_increments<S: Into<Size>>(&self, increments: Option<S>) {
let increments = increments.map(Into::into);
self.window self.window
.set_resize_increments(increments.map(Into::into)) .maybe_queue_on_main(move |w| w.set_resize_increments(increments))
} }
} }
@ -842,7 +863,7 @@ impl Window {
/// - **iOS / Android:** Unsupported. /// - **iOS / Android:** Unsupported.
#[inline] #[inline]
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
self.window.set_title(title) self.window.maybe_wait_on_main(|w| w.set_title(title))
} }
/// Change the window transparency state. /// Change the window transparency state.
@ -859,7 +880,8 @@ impl Window {
/// - **Windows / X11 / Web / iOS / Android / Orbital:** Unsupported. /// - **Windows / X11 / Web / iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_transparent(&self, transparent: bool) { pub fn set_transparent(&self, transparent: bool) {
self.window.set_transparent(transparent) self.window
.maybe_queue_on_main(move |w| w.set_transparent(transparent))
} }
/// Modifies the window's visibility. /// Modifies the window's visibility.
@ -872,7 +894,8 @@ impl Window {
/// - **iOS:** Can only be called on the main thread. /// - **iOS:** Can only be called on the main thread.
#[inline] #[inline]
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
self.window.set_visible(visible) self.window
.maybe_queue_on_main(move |w| w.set_visible(visible))
} }
/// Gets the window's current visibility state. /// Gets the window's current visibility state.
@ -885,7 +908,7 @@ impl Window {
/// - **Wayland / iOS / Android / Web:** Unsupported. /// - **Wayland / iOS / Android / Web:** Unsupported.
#[inline] #[inline]
pub fn is_visible(&self) -> Option<bool> { pub fn is_visible(&self) -> Option<bool> {
self.window.is_visible() self.window.maybe_wait_on_main(|w| w.is_visible())
} }
/// Sets whether the window is resizable or not. /// Sets whether the window is resizable or not.
@ -904,7 +927,8 @@ impl Window {
/// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
#[inline] #[inline]
pub fn set_resizable(&self, resizable: bool) { pub fn set_resizable(&self, resizable: bool) {
self.window.set_resizable(resizable) self.window
.maybe_queue_on_main(move |w| w.set_resizable(resizable))
} }
/// Gets the window's current resizable state. /// Gets the window's current resizable state.
@ -915,7 +939,7 @@ impl Window {
/// - **iOS / Android / Web:** Unsupported. /// - **iOS / Android / Web:** Unsupported.
#[inline] #[inline]
pub fn is_resizable(&self) -> bool { pub fn is_resizable(&self) -> bool {
self.window.is_resizable() self.window.maybe_wait_on_main(|w| w.is_resizable())
} }
/// Sets the enabled window buttons. /// Sets the enabled window buttons.
@ -925,7 +949,8 @@ impl Window {
/// - **Wayland / X11 / Orbital:** Not implemented. /// - **Wayland / X11 / Orbital:** Not implemented.
/// - **Web / iOS / Android:** Unsupported. /// - **Web / iOS / Android:** Unsupported.
pub fn set_enabled_buttons(&self, buttons: WindowButtons) { pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.window.set_enabled_buttons(buttons) self.window
.maybe_queue_on_main(move |w| w.set_enabled_buttons(buttons))
} }
/// Gets the enabled window buttons. /// Gets the enabled window buttons.
@ -935,7 +960,7 @@ impl Window {
/// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`]. /// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`].
/// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`].
pub fn enabled_buttons(&self) -> WindowButtons { pub fn enabled_buttons(&self) -> WindowButtons {
self.window.enabled_buttons() self.window.maybe_wait_on_main(|w| w.enabled_buttons())
} }
/// Sets the window to minimized or back /// Sets the window to minimized or back
@ -946,7 +971,8 @@ impl Window {
/// - **Wayland:** Un-minimize is unsupported. /// - **Wayland:** Un-minimize is unsupported.
#[inline] #[inline]
pub fn set_minimized(&self, minimized: bool) { pub fn set_minimized(&self, minimized: bool) {
self.window.set_minimized(minimized); self.window
.maybe_queue_on_main(move |w| w.set_minimized(minimized))
} }
/// Gets the window's current minimized state. /// Gets the window's current minimized state.
@ -963,7 +989,7 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
#[inline] #[inline]
pub fn is_minimized(&self) -> Option<bool> { pub fn is_minimized(&self) -> Option<bool> {
self.window.is_minimized() self.window.maybe_wait_on_main(|w| w.is_minimized())
} }
/// Sets the window to maximized or back. /// Sets the window to maximized or back.
@ -973,7 +999,8 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_maximized(&self, maximized: bool) { pub fn set_maximized(&self, maximized: bool) {
self.window.set_maximized(maximized) self.window
.maybe_queue_on_main(move |w| w.set_maximized(maximized))
} }
/// Gets the window's current maximized state. /// Gets the window's current maximized state.
@ -983,7 +1010,7 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
#[inline] #[inline]
pub fn is_maximized(&self) -> bool { pub fn is_maximized(&self) -> bool {
self.window.is_maximized() self.window.maybe_wait_on_main(|w| w.is_maximized())
} }
/// Sets the window to fullscreen or back. /// Sets the window to fullscreen or back.
@ -1012,7 +1039,8 @@ impl Window {
/// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation /// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation
#[inline] #[inline]
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.window.set_fullscreen(fullscreen.map(|f| f.into())) self.window
.maybe_queue_on_main(move |w| w.set_fullscreen(fullscreen.map(|f| f.into())))
} }
/// Gets the window's current fullscreen state. /// Gets the window's current fullscreen state.
@ -1024,7 +1052,8 @@ impl Window {
/// - **Wayland:** Can return `Borderless(None)` when there are no monitors. /// - **Wayland:** Can return `Borderless(None)` when there are no monitors.
#[inline] #[inline]
pub fn fullscreen(&self) -> Option<Fullscreen> { pub fn fullscreen(&self) -> Option<Fullscreen> {
self.window.fullscreen().map(|f| f.into()) self.window
.maybe_wait_on_main(|w| w.fullscreen().map(|f| f.into()))
} }
/// Turn window decorations on or off. /// Turn window decorations on or off.
@ -1038,7 +1067,8 @@ impl Window {
/// - **iOS / Android / Web:** No effect. /// - **iOS / Android / Web:** No effect.
#[inline] #[inline]
pub fn set_decorations(&self, decorations: bool) { pub fn set_decorations(&self, decorations: bool) {
self.window.set_decorations(decorations) self.window
.maybe_queue_on_main(move |w| w.set_decorations(decorations))
} }
/// Gets the window's current decorations state. /// Gets the window's current decorations state.
@ -1051,7 +1081,7 @@ impl Window {
/// - **iOS / Android / Web:** Always returns `true`. /// - **iOS / Android / Web:** Always returns `true`.
#[inline] #[inline]
pub fn is_decorated(&self) -> bool { pub fn is_decorated(&self) -> bool {
self.window.is_decorated() self.window.maybe_wait_on_main(|w| w.is_decorated())
} }
/// Change the window level. /// Change the window level.
@ -1060,7 +1090,8 @@ impl Window {
/// ///
/// See [`WindowLevel`] for details. /// See [`WindowLevel`] for details.
pub fn set_window_level(&self, level: WindowLevel) { pub fn set_window_level(&self, level: WindowLevel) {
self.window.set_window_level(level) self.window
.maybe_queue_on_main(move |w| w.set_window_level(level))
} }
/// Sets the window icon. /// Sets the window icon.
@ -1079,7 +1110,8 @@ impl Window {
/// said, it's usually in the same ballpark as on Windows. /// said, it's usually in the same ballpark as on Windows.
#[inline] #[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) { pub fn set_window_icon(&self, window_icon: Option<Icon>) {
self.window.set_window_icon(window_icon) self.window
.maybe_queue_on_main(move |w| w.set_window_icon(window_icon))
} }
/// Set the IME cursor editing area, where the `position` is the top left corner of that area /// Set the IME cursor editing area, where the `position` is the top left corner of that area
@ -1119,8 +1151,10 @@ impl Window {
/// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0
#[inline] #[inline]
pub fn set_ime_cursor_area<P: Into<Position>, S: Into<Size>>(&self, position: P, size: S) { pub fn set_ime_cursor_area<P: Into<Position>, S: Into<Size>>(&self, position: P, size: S) {
let position = position.into();
let size = size.into();
self.window self.window
.set_ime_cursor_area(position.into(), size.into()) .maybe_queue_on_main(move |w| w.set_ime_cursor_area(position, size))
} }
/// Sets whether the window should get IME events /// Sets whether the window should get IME events
@ -1145,7 +1179,8 @@ impl Window {
/// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput
#[inline] #[inline]
pub fn set_ime_allowed(&self, allowed: bool) { pub fn set_ime_allowed(&self, allowed: bool) {
self.window.set_ime_allowed(allowed); self.window
.maybe_queue_on_main(move |w| w.set_ime_allowed(allowed))
} }
/// Sets the IME purpose for the window using [`ImePurpose`]. /// Sets the IME purpose for the window using [`ImePurpose`].
@ -1155,7 +1190,8 @@ impl Window {
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) { pub fn set_ime_purpose(&self, purpose: ImePurpose) {
self.window.set_ime_purpose(purpose); self.window
.maybe_queue_on_main(move |w| w.set_ime_purpose(purpose))
} }
/// Brings the window to the front and sets input focus. Has no effect if the window is /// Brings the window to the front and sets input focus. Has no effect if the window is
@ -1170,7 +1206,7 @@ impl Window {
/// - **iOS / Android / Wayland / Orbital:** Unsupported. /// - **iOS / Android / Wayland / Orbital:** Unsupported.
#[inline] #[inline]
pub fn focus_window(&self) { pub fn focus_window(&self) {
self.window.focus_window() self.window.maybe_queue_on_main(|w| w.focus_window())
} }
/// Gets whether the window has keyboard focus. /// Gets whether the window has keyboard focus.
@ -1180,7 +1216,7 @@ impl Window {
/// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused
#[inline] #[inline]
pub fn has_focus(&self) -> bool { pub fn has_focus(&self) -> bool {
self.window.has_focus() self.window.maybe_wait_on_main(|w| w.has_focus())
} }
/// Requests user attention to the window, this has no effect if the application /// Requests user attention to the window, this has no effect if the application
@ -1198,7 +1234,8 @@ impl Window {
/// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
#[inline] #[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) { pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.window.request_user_attention(request_type) self.window
.maybe_queue_on_main(move |w| w.request_user_attention(request_type))
} }
/// Sets the current window theme. Use `None` to fallback to system default. /// Sets the current window theme. Use `None` to fallback to system default.
@ -1212,7 +1249,7 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Unsupported. /// - **iOS / Android / Web / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_theme(&self, theme: Option<Theme>) { pub fn set_theme(&self, theme: Option<Theme>) {
self.window.set_theme(theme) self.window.maybe_queue_on_main(move |w| w.set_theme(theme))
} }
/// Returns the current window theme. /// Returns the current window theme.
@ -1223,7 +1260,7 @@ impl Window {
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
#[inline] #[inline]
pub fn theme(&self) -> Option<Theme> { pub fn theme(&self) -> Option<Theme> {
self.window.theme() self.window.maybe_wait_on_main(|w| w.theme())
} }
/// Prevents the window contents from being captured by other apps. /// Prevents the window contents from being captured by other apps.
@ -1237,7 +1274,8 @@ impl Window {
/// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone
pub fn set_content_protected(&self, _protected: bool) { pub fn set_content_protected(&self, _protected: bool) {
#[cfg(any(macos_platform, windows_platform))] #[cfg(any(macos_platform, windows_platform))]
self.window.set_content_protected(_protected); self.window
.maybe_queue_on_main(move |w| w.set_content_protected(_protected))
} }
/// Gets the current title of the window. /// Gets the current title of the window.
@ -1247,7 +1285,7 @@ impl Window {
/// - **iOS / Android / x11 / Wayland / Web:** Unsupported. Always returns an empty string. /// - **iOS / Android / x11 / Wayland / Web:** Unsupported. Always returns an empty string.
#[inline] #[inline]
pub fn title(&self) -> String { pub fn title(&self) -> String {
self.window.title() self.window.maybe_wait_on_main(|w| w.title())
} }
} }
@ -1260,7 +1298,8 @@ impl Window {
/// - **iOS / Android / Orbital:** Unsupported. /// - **iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
self.window.set_cursor_icon(cursor); self.window
.maybe_queue_on_main(move |w| w.set_cursor_icon(cursor))
} }
/// Changes the position of the cursor in window coordinates. /// Changes the position of the cursor in window coordinates.
@ -1283,7 +1322,9 @@ impl Window {
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`]. /// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`].
#[inline] #[inline]
pub fn set_cursor_position<P: Into<Position>>(&self, position: P) -> Result<(), ExternalError> { pub fn set_cursor_position<P: Into<Position>>(&self, position: P) -> Result<(), ExternalError> {
self.window.set_cursor_position(position.into()) let position = position.into();
self.window
.maybe_wait_on_main(|w| w.set_cursor_position(position))
} }
/// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window. /// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window.
@ -1303,7 +1344,7 @@ impl Window {
/// ``` /// ```
#[inline] #[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window.set_cursor_grab(mode) self.window.maybe_wait_on_main(|w| w.set_cursor_grab(mode))
} }
/// Modifies the cursor's visibility. /// Modifies the cursor's visibility.
@ -1320,7 +1361,8 @@ impl Window {
/// - **iOS / Android / Orbital:** Unsupported. /// - **iOS / Android / Orbital:** Unsupported.
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
self.window.set_cursor_visible(visible) self.window
.maybe_queue_on_main(move |w| w.set_cursor_visible(visible))
} }
/// Moves the window with the left mouse button until the button is released. /// Moves the window with the left mouse button until the button is released.
@ -1336,7 +1378,7 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
#[inline] #[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> { pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.drag_window() self.window.maybe_wait_on_main(|w| w.drag_window())
} }
/// Resizes the window with the left mouse button until the button is released. /// Resizes the window with the left mouse button until the button is released.
@ -1350,7 +1392,8 @@ impl Window {
/// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`].
#[inline] #[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window.drag_resize_window(direction) self.window
.maybe_wait_on_main(|w| w.drag_resize_window(direction))
} }
/// Modifies whether the window catches cursor events. /// Modifies whether the window catches cursor events.
@ -1363,7 +1406,8 @@ impl Window {
/// - **iOS / Android / Web / X11 / Orbital:** Always returns an [`ExternalError::NotSupported`]. /// - **iOS / Android / Web / X11 / Orbital:** Always returns an [`ExternalError::NotSupported`].
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.window.set_cursor_hittest(hittest) self.window
.maybe_wait_on_main(|w| w.set_cursor_hittest(hittest))
} }
} }
@ -1379,8 +1423,7 @@ impl Window {
#[inline] #[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> { pub fn current_monitor(&self) -> Option<MonitorHandle> {
self.window self.window
.current_monitor() .maybe_wait_on_main(|w| w.current_monitor().map(|inner| MonitorHandle { inner }))
.map(|inner| MonitorHandle { inner })
} }
/// Returns the list of all the monitors available on the system. /// Returns the list of all the monitors available on the system.
@ -1394,11 +1437,11 @@ impl Window {
/// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors /// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors
#[inline] #[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> { pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
#[allow(clippy::useless_conversion)] // false positive on some platforms self.window.maybe_wait_on_main(|w| {
self.window w.available_monitors()
.available_monitors() .into_iter()
.into_iter() .map(|inner| MonitorHandle { inner })
.map(|inner| MonitorHandle { inner }) })
} }
/// Returns the primary monitor of the system. /// Returns the primary monitor of the system.
@ -1416,10 +1459,10 @@ impl Window {
#[inline] #[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.window self.window
.primary_monitor() .maybe_wait_on_main(|w| w.primary_monitor().map(|inner| MonitorHandle { inner }))
.map(|inner| MonitorHandle { inner })
} }
} }
unsafe impl HasRawWindowHandle for Window { unsafe impl HasRawWindowHandle for Window {
/// Returns a [`raw_window_handle::RawWindowHandle`] for the Window /// Returns a [`raw_window_handle::RawWindowHandle`] for the Window
/// ///
@ -1437,7 +1480,11 @@ unsafe impl HasRawWindowHandle for Window {
/// [`Event::Resumed`]: crate::event::Event::Resumed /// [`Event::Resumed`]: crate::event::Event::Resumed
/// [`Event::Suspended`]: crate::event::Event::Suspended /// [`Event::Suspended`]: crate::event::Event::Suspended
fn raw_window_handle(&self) -> RawWindowHandle { fn raw_window_handle(&self) -> RawWindowHandle {
self.window.raw_window_handle() struct Wrapper(RawWindowHandle);
unsafe impl Send for Wrapper {}
self.window
.maybe_wait_on_main(|w| Wrapper(w.raw_window_handle()))
.0
} }
} }
@ -1447,7 +1494,11 @@ unsafe impl HasRawDisplayHandle for Window {
/// ///
/// [`EventLoop`]: crate::event_loop::EventLoop /// [`EventLoop`]: crate::event_loop::EventLoop
fn raw_display_handle(&self) -> RawDisplayHandle { fn raw_display_handle(&self) -> RawDisplayHandle {
self.window.raw_display_handle() struct Wrapper(RawDisplayHandle);
unsafe impl Send for Wrapper {}
self.window
.maybe_wait_on_main(|w| Wrapper(w.raw_display_handle()))
.0
} }
} }