diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb55550..cbccefec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`. - Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable. - **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs. +- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it. +- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead. +- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods). +- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods). +- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods). # 0.26.1 (2022-01-05) diff --git a/examples/custom_events.rs b/examples/custom_events.rs index 016754b5..8337ab4d 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -3,7 +3,7 @@ fn main() { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::{ControlFlow, EventLoopBuilder}, window::WindowBuilder, }; @@ -13,7 +13,7 @@ fn main() { } SimpleLogger::new().init().unwrap(); - let event_loop = EventLoop::::with_user_event(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let _window = WindowBuilder::new() .with_title("A fantastic window!") diff --git a/src/event_loop.rs b/src/event_loop.rs index 17c01f5f..42d27280 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -10,6 +10,7 @@ //! [event_loop_proxy]: crate::event_loop::EventLoopProxy //! [send_event]: crate::event_loop::EventLoopProxy::send_event use instant::Instant; +use std::marker::PhantomData; use std::ops::Deref; use std::{error, fmt}; @@ -31,7 +32,7 @@ use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// pub struct EventLoop { pub(crate) event_loop: platform_impl::EventLoop, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync + pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } /// Target that associates windows with an `EventLoop`. @@ -42,7 +43,62 @@ pub struct EventLoop { /// `&EventLoop`. pub struct EventLoopWindowTarget { pub(crate) p: platform_impl::EventLoopWindowTarget, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync + pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync +} + +/// Object that allows building the event loop. +/// +/// This is used to make specifying options that affect the whole application +/// easier. But note that constructing multiple event loops is not supported. +#[derive(Debug, Clone, Default)] +pub struct EventLoopBuilder { + pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, + _p: PhantomData, +} + +impl EventLoopBuilder<()> { + /// Start building a new event loop. + #[inline] + pub fn new() -> Self { + Self::with_user_event() + } +} + +impl EventLoopBuilder { + /// Start building a new event loop, with the given type as the user event + /// type. + #[inline] + pub fn with_user_event() -> Self { + Self { + platform_specific: Default::default(), + _p: PhantomData, + } + } + + /// Builds a new event loop. + /// + /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.*** + /// Attempting to create the event loop on a different thread will panic. This restriction isn't + /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when + /// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed + /// in the relevant `platform` module if the target platform supports creating an event loop on + /// any thread. + /// + /// Usage will result in display backend initialisation, this can be controlled on linux + /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. + /// If it is not set, winit will try to connect to a wayland connection, and if it fails will + /// fallback on x11. If this variable is set with any other value, winit will panic. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn build(&mut self) -> EventLoop { + EventLoop { + event_loop: platform_impl::EventLoop::new(&self.platform_specific), + _marker: PhantomData, + } + } } impl fmt::Debug for EventLoop { @@ -119,41 +175,17 @@ impl Default for ControlFlow { } impl EventLoop<()> { - /// Builds a new event loop with a `()` as the user event type. - /// - /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.*** - /// Attempting to create the event loop on a different thread will panic. This restriction isn't - /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when - /// porting to platforms that require it. `EventLoopExt::new_any_thread` functions are exposed - /// in the relevant `platform` module if the target platform supports creating an event loop on - /// any thread. - /// - /// Usage will result in display backend initialisation, this can be controlled on linux - /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. - /// If it is not set, winit will try to connect to a wayland connection, and if it fails will - /// fallback on x11. If this variable is set with any other value, winit will panic. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. + /// Alias for `EventLoopBuilder::new().build()`. + #[inline] pub fn new() -> EventLoop<()> { - EventLoop::<()>::with_user_event() + EventLoopBuilder::new().build() } } impl EventLoop { - /// Builds a new event loop. - /// - /// All caveats documented in [`EventLoop::new`] apply to this function. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. + #[deprecated = "Use `EventLoopBuiler::::with_user_event().build()` instead."] pub fn with_user_event() -> EventLoop { - EventLoop { - event_loop: platform_impl::EventLoop::new(), - _marker: ::std::marker::PhantomData, - } + EventLoopBuilder::::with_user_event().build() } /// Hijacks the calling thread and initializes the winit event loop with the provided diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 6a8c2948..1ddab448 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,9 +4,8 @@ use std::os::raw::c_void; use crate::{ dpi::LogicalSize, - event_loop::{EventLoop, EventLoopWindowTarget}, + event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, - platform_impl::get_aux_state_mut, window::{Window, WindowBuilder}, }; @@ -74,7 +73,7 @@ impl WindowExtMacOS for Window { } /// Corresponds to `NSApplicationActivationPolicy`. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ActivationPolicy { /// Corresponds to `NSApplicationActivationPolicyRegular`. Regular, @@ -179,36 +178,63 @@ impl WindowBuilderExtMacOS for WindowBuilder { } } -pub trait EventLoopExtMacOS { - /// Sets the activation policy for the application. It is set to - /// `NSApplicationActivationPolicyRegular` by default. +pub trait EventLoopBuilderExtMacOS { + /// Sets the activation policy for the application. /// - /// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or - /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) - fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); + /// It is set to [`ActivationPolicy::Regular`] by default. + /// + /// # Example + /// + /// Set the activation policy to "accessory". + /// + /// ``` + /// use winit::event_loop::EventLoopBuilder; + /// #[cfg(target_os = "macos")] + /// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy}; + /// + /// let mut builder = EventLoopBuilder::new(); + /// #[cfg(target_os = "macos")] + /// builder.with_activation_policy(ActivationPolicy::Accessory); + /// # if false { // We can't test this part + /// let event_loop = builder.build(); + /// # } + /// ``` + fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self; - /// Used to prevent a default menubar menu from getting created + /// Used to control whether a default menubar menu is created. /// - /// The default menu creation is enabled by default. + /// Menu creation is enabled by default. /// - /// This function only takes effect if it's called before calling - /// [`run`](crate::event_loop::EventLoop::run) or - /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) - fn enable_default_menu_creation(&mut self, enable: bool); + /// # Example + /// + /// Disable creating a default menubar. + /// + /// ``` + /// use winit::event_loop::EventLoopBuilder; + /// #[cfg(target_os = "macos")] + /// use winit::platform::macos::EventLoopBuilderExtMacOS; + /// + /// let mut builder = EventLoopBuilder::new(); + /// #[cfg(target_os = "macos")] + /// builder.with_default_menu(false); + /// # if false { // We can't test this part + /// let event_loop = builder.build(); + /// # } + /// ``` + fn with_default_menu(&mut self, enable: bool) -> &mut Self; } -impl EventLoopExtMacOS for EventLoop { + +impl EventLoopBuilderExtMacOS for EventLoopBuilder { #[inline] - fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { - unsafe { - get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy; - } + fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self { + self.platform_specific.activation_policy = activation_policy; + self } #[inline] - fn enable_default_menu_creation(&mut self, enable: bool) { - unsafe { - get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable; - } + fn with_default_menu(&mut self, enable: bool) -> &mut Self { + self.platform_specific.default_menu = enable; + self } } diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 74dc769b..43c11b00 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -11,7 +11,7 @@ use std::os::raw; use std::{ptr, sync::Arc}; use crate::{ - event_loop::{EventLoop, EventLoopWindowTarget}, + event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; @@ -21,8 +21,7 @@ use crate::dpi::Size; #[cfg(feature = "x11")] use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; use crate::platform_impl::{ - EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, - Window as LinuxWindow, + Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow, }; // TODO: stupid hack so that glutin can do its work @@ -93,100 +92,42 @@ impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { } } -/// Additional methods on `EventLoop` that are specific to Unix. -pub trait EventLoopExtUnix { - /// Builds a new `EventLoop` that is forced to use X11. - /// - /// # Panics - /// - /// If called outside the main thread. To initialize an X11 event loop outside - /// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread). +/// Additional methods on [`EventLoopBuilder`] that are specific to Unix. +pub trait EventLoopBuilderExtUnix { + /// Force using X11. #[cfg(feature = "x11")] - fn new_x11() -> Result - where - Self: Sized; + fn with_x11(&mut self) -> &mut Self; - /// Builds a new `EventLoop` that is forced to use Wayland. - /// - /// # Panics - /// - /// If called outside the main thread. To initialize a Wayland event loop outside - /// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread). + /// Force using Wayland. #[cfg(feature = "wayland")] - fn new_wayland() -> Self - where - Self: Sized; + fn with_wayland(&mut self) -> &mut Self; - /// Builds a new `EventLoop` on any thread. + /// Whether to allow the event loop to be created off of the main thread. /// - /// This method bypasses the cross-platform compatibility requirement - /// that `EventLoop` be created on the main thread. - fn new_any_thread() -> Self - where - Self: Sized; - - /// Builds a new X11 `EventLoop` on any thread. - /// - /// This method bypasses the cross-platform compatibility requirement - /// that `EventLoop` be created on the main thread. - #[cfg(feature = "x11")] - fn new_x11_any_thread() -> Result - where - Self: Sized; - - /// Builds a new Wayland `EventLoop` on any thread. - /// - /// This method bypasses the cross-platform compatibility requirement - /// that `EventLoop` be created on the main thread. - #[cfg(feature = "wayland")] - fn new_wayland_any_thread() -> Self - where - Self: Sized; + /// By default, the window is only allowed to be created on the main + /// thread, to make platform compatibility easier. + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } -fn wrap_ev(event_loop: LinuxEventLoop) -> EventLoop { - EventLoop { - event_loop, - _marker: std::marker::PhantomData, - } -} - -impl EventLoopExtUnix for EventLoop { - #[inline] - fn new_any_thread() -> Self { - wrap_ev(LinuxEventLoop::new_any_thread()) - } - +impl EventLoopBuilderExtUnix for EventLoopBuilder { #[inline] #[cfg(feature = "x11")] - fn new_x11_any_thread() -> Result { - LinuxEventLoop::new_x11_any_thread().map(wrap_ev) + fn with_x11(&mut self) -> &mut Self { + self.platform_specific.forced_backend = Some(Backend::X); + self } #[inline] #[cfg(feature = "wayland")] - fn new_wayland_any_thread() -> Self { - wrap_ev( - LinuxEventLoop::new_wayland_any_thread() - // TODO: propagate - .expect("failed to open Wayland connection"), - ) + fn with_wayland(&mut self) -> &mut Self { + self.platform_specific.forced_backend = Some(Backend::Wayland); + self } #[inline] - #[cfg(feature = "x11")] - fn new_x11() -> Result { - LinuxEventLoop::new_x11().map(wrap_ev) - } - - #[inline] - #[cfg(feature = "wayland")] - fn new_wayland() -> Self { - wrap_ev( - LinuxEventLoop::new_wayland() - // TODO: propagate - .expect("failed to open Wayland connection"), - ) + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { + self.platform_specific.any_thread = any_thread; + self } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 3499ba77..3cd7c5c3 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -9,62 +9,61 @@ use winapi::shared::windef::{HMENU, HWND}; use crate::{ dpi::PhysicalSize, event::DeviceId, - event_loop::EventLoop, + event_loop::EventLoopBuilder, monitor::MonitorHandle, - platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon}, + platform_impl::{Parent, WinIcon}, window::{BadIcon, Icon, Theme, Window, WindowBuilder}, }; /// Additional methods on `EventLoop` that are specific to Windows. -pub trait EventLoopExtWindows { - /// Creates an event loop off of the main thread. +pub trait EventLoopBuilderExtWindows { + /// Whether to allow the event loop to be created off of the main thread. + /// + /// By default, the window is only allowed to be created on the main + /// thread, to make platform compatibility easier. /// /// # `Window` caveats /// /// Note that any `Window` created on the new thread will be destroyed when the thread /// terminates. Attempting to use a `Window` after its parent thread terminates has /// unspecified, although explicitly not undefined, behavior. - fn new_any_thread() -> Self - where - Self: Sized; + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; - /// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's - /// undesirable, you can create an `EventLoop` using this function instead. - fn new_dpi_unaware() -> Self - where - Self: Sized; - - /// Creates a DPI-unaware event loop off of the main thread. + /// Whether to enable process-wide DPI awareness. /// - /// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here. - fn new_dpi_unaware_any_thread() -> Self - where - Self: Sized; + /// By default, `winit` will attempt to enable process-wide DPI awareness. If + /// that's undesirable, you can disable it with this function. + /// + /// # Example + /// + /// Disable process-wide DPI awareness. + /// + /// ``` + /// use winit::event_loop::EventLoopBuilder; + /// #[cfg(target_os = "windows")] + /// use winit::platform::windows::EventLoopBuilderExtWindows; + /// + /// let mut builder = EventLoopBuilder::new(); + /// #[cfg(target_os = "windows")] + /// builder.with_dpi_aware(false); + /// # if false { // We can't test this part + /// let event_loop = builder.build(); + /// # } + /// ``` + fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self; } -impl EventLoopExtWindows for EventLoop { +impl EventLoopBuilderExtWindows for EventLoopBuilder { #[inline] - fn new_any_thread() -> Self { - EventLoop { - event_loop: WindowsEventLoop::new_any_thread(), - _marker: ::std::marker::PhantomData, - } + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { + self.platform_specific.any_thread = any_thread; + self } #[inline] - fn new_dpi_unaware() -> Self { - EventLoop { - event_loop: WindowsEventLoop::new_dpi_unaware(), - _marker: ::std::marker::PhantomData, - } - } - - #[inline] - fn new_dpi_unaware_any_thread() -> Self { - EventLoop { - event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(), - _marker: ::std::marker::PhantomData, - } + fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self { + self.platform_specific.dpi_aware = dpi_aware; + self } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index dae27493..0b3bcde7 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -71,6 +71,9 @@ pub struct EventLoop { running: bool, } +#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + macro_rules! call_event_handler { ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ if let ControlFlow::ExitWithCode(code) = $cf { @@ -82,7 +85,7 @@ macro_rules! call_event_handler { } impl EventLoop { - pub fn new() -> Self { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { Self { window_target: event_loop::EventLoopWindowTarget { p: EventLoopWindowTarget { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 1680c98a..feb6b7ea 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -69,8 +69,11 @@ pub struct EventLoop { window_target: RootEventLoopWindowTarget, } +#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + impl EventLoop { - pub fn new() -> EventLoop { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop { static mut SINGLETON_INIT: bool = false; unsafe { assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index b461785f..53c230f3 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -78,8 +78,10 @@ mod window; use std::fmt; -pub use self::{ - event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, +pub(crate) use self::{ + event_loop::{ + EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, + }, monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, }; diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index b8e23365..ea9cd4ae 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -49,6 +49,29 @@ pub mod x11; /// If this variable is set with any other value, winit will panic. const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) enum Backend { + #[cfg(feature = "x11")] + X, + #[cfg(feature = "wayland")] + Wayland, +} + +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) forced_backend: Option, + pub(crate) any_thread: bool, +} + +impl Default for PlatformSpecificEventLoopAttributes { + fn default() -> Self { + Self { + forced_backend: None, + any_thread: false, + } + } +} + #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { #[cfg(feature = "x11")] @@ -568,13 +591,28 @@ impl Clone for EventLoopProxy { } impl EventLoop { - pub fn new() -> EventLoop { - assert_is_main_thread("new_any_thread"); + pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { + if !attributes.any_thread && !is_main_thread() { + panic!( + "Initializing the event loop outside of the main thread is a significant \ + cross-platform compatibility hazard. If you absolutely need to create an \ + EventLoop on a different thread, you can use the \ + `EventLoopBuilderExtUnix::any_thread` function." + ); + } - EventLoop::new_any_thread() - } + #[cfg(feature = "x11")] + if attributes.forced_backend == Some(Backend::X) { + // TODO: Propagate + return EventLoop::new_x11_any_thread().unwrap(); + } + + #[cfg(feature = "wayland")] + if attributes.forced_backend == Some(Backend::Wayland) { + // TODO: Propagate + return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection"); + } - pub fn new_any_thread() -> EventLoop { if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { match env_var.as_str() { "x11" => { @@ -623,26 +661,12 @@ impl EventLoop { } #[cfg(feature = "wayland")] - pub fn new_wayland() -> Result, Box> { - assert_is_main_thread("new_wayland_any_thread"); - - EventLoop::new_wayland_any_thread() - } - - #[cfg(feature = "wayland")] - pub fn new_wayland_any_thread() -> Result, Box> { + fn new_wayland_any_thread() -> Result, Box> { wayland::EventLoop::new().map(EventLoop::Wayland) } #[cfg(feature = "x11")] - pub fn new_x11() -> Result, XNotSupported> { - assert_is_main_thread("new_x11_any_thread"); - - EventLoop::new_x11_any_thread() - } - - #[cfg(feature = "x11")] - pub fn new_x11_any_thread() -> Result, XNotSupported> { + fn new_x11_any_thread() -> Result, XNotSupported> { let xconn = match X11_BACKEND.lock().as_ref() { Ok(xconn) => xconn.clone(), Err(err) => return Err(err.clone()), @@ -750,17 +774,6 @@ fn sticky_exit_callback( } } -fn assert_is_main_thread(suggested_method: &str) { - if !is_main_thread() { - panic!( - "Initializing the event loop outside of the main thread is a significant \ - cross-platform compatibility hazard. If you really, absolutely need to create an \ - EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.", - suggested_method - ); - } -} - #[cfg(target_os = "linux")] fn is_main_thread() -> bool { use libc::{c_long, getpid, syscall, SYS_gettid}; diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 6840497a..940a5538 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -13,12 +13,8 @@ use std::{ static AUX_DELEGATE_STATE_NAME: &str = "auxState"; pub struct AuxDelegateState { - /// We store this value in order to be able to defer setting the activation policy until - /// after the app has finished launching. If the activation policy is set earlier, the - /// menubar is initially unresponsive on macOS 10.15 for example. pub activation_policy: ActivationPolicy, - - pub create_default_menu: bool, + pub default_menu: bool, } pub struct AppDelegateClass(pub *const Class); @@ -54,11 +50,12 @@ extern "C" fn new(class: &Class, _: Sel) -> id { unsafe { let this: id = msg_send![class, alloc]; let this: id = msg_send![this, init]; + // TODO: Remove the need for this initialization here (*this).set_ivar( AUX_DELEGATE_STATE_NAME, Box::into_raw(Box::new(RefCell::new(AuxDelegateState { activation_policy: ActivationPolicy::Regular, - create_default_menu: true, + default_menu: true, }))) as *mut c_void, ); this diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index d1d9524c..7b392a41 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -296,7 +296,7 @@ impl AppState { }; HANDLER.set_ready(); HANDLER.waker().start(); - let create_default_menu = unsafe { get_aux_state_mut(app_delegate).create_default_menu }; + let create_default_menu = unsafe { get_aux_state_mut(app_delegate).default_menu }; if create_default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created @@ -486,7 +486,7 @@ fn apply_activation_policy(app_delegate: &Object) { let ns_app = NSApp(); // We need to delay setting the activation policy and activating the app // until `applicationDidFinishLaunching` has been called. Otherwise the - // menu bar won't be interactable. + // menu bar is initially unresponsive on macOS 10.15. let act_pol = get_aux_state_mut(app_delegate).activation_policy; ns_app.setActivationPolicy_(match act_pol { ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 54181d36..ab82e52e 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -22,13 +22,17 @@ use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::{ - app::APP_CLASS, - app_delegate::APP_DELEGATE_CLASS, - app_state::{AppState, Callback}, - monitor::{self, MonitorHandle}, - observer::*, - util::IdRef, + platform::macos::ActivationPolicy, + platform_impl::{ + get_aux_state_mut, + platform::{ + app::APP_CLASS, + app_delegate::APP_DELEGATE_CLASS, + app_state::{AppState, Callback}, + monitor::{self, MonitorHandle}, + observer::*, + util::IdRef, + }, }, }; @@ -100,7 +104,9 @@ impl EventLoopWindowTarget { } pub struct EventLoop { - pub(crate) delegate: IdRef, + /// The delegate is only weakly referenced by NSApplication, so we keep + /// it around here as well. + _delegate: IdRef, window_target: Rc>, panic_info: Rc, @@ -114,8 +120,23 @@ pub struct EventLoop { _callback: Option>>, } +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) activation_policy: ActivationPolicy, + pub(crate) default_menu: bool, +} + +impl Default for PlatformSpecificEventLoopAttributes { + fn default() -> Self { + Self { + activation_policy: Default::default(), // Regular + default_menu: true, + } + } +} + impl EventLoop { - pub fn new() -> Self { + pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { let delegate = unsafe { let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread); if is_main_thread == NO { @@ -129,15 +150,21 @@ impl EventLoop { let app: id = msg_send![APP_CLASS.0, sharedApplication]; let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); + + let mut aux_state = get_aux_state_mut(&**delegate); + aux_state.activation_policy = attributes.activation_policy; + aux_state.default_menu = attributes.default_menu; + autoreleasepool(|| { let _: () = msg_send![app, setDelegate:*delegate]; }); + delegate }; let panic_info: Rc = Default::default(); setup_control_flow_observers(Rc::downgrade(&panic_info)); EventLoop { - delegate, + _delegate: delegate, window_target: Rc::new(RootWindowTarget { p: Default::default(), _marker: PhantomData, diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index af2d384d..987b4785 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -18,9 +18,12 @@ mod window_delegate; use std::{fmt, ops::Deref, sync::Arc}; -pub use self::{ - app_delegate::{get_aux_state_mut, AuxDelegateState}, - event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, +pub(crate) use self::{ + app_delegate::get_aux_state_mut, + event_loop::{ + EventLoop, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, + Proxy as EventLoopProxy, + }, monitor::{MonitorHandle, VideoMode}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, }; diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 24be4d3d..da407339 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -16,8 +16,11 @@ pub struct EventLoop { elw: root::EventLoopWindowTarget, } +#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + impl EventLoop { - pub fn new() -> Self { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { EventLoop { elw: root::EventLoopWindowTarget { p: WindowTarget::new(), diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 8c7f993f..50c14442 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -28,8 +28,9 @@ mod backend; pub use self::device::Id as DeviceId; pub use self::error::OsError; -pub use self::event_loop::{ - EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget, +pub(crate) use self::event_loop::{ + EventLoop, PlatformSpecificEventLoopAttributes, Proxy as EventLoopProxy, + WindowTarget as EventLoopWindowTarget, }; pub use self::monitor::{Handle as MonitorHandle, Mode as VideoMode}; pub use self::window::{ diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 24183154..fd738dde 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -113,48 +113,44 @@ pub struct EventLoop { window_target: RootELW, } +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) any_thread: bool, + pub(crate) dpi_aware: bool, +} + +impl Default for PlatformSpecificEventLoopAttributes { + fn default() -> Self { + Self { + any_thread: false, + dpi_aware: true, + } + } +} + pub struct EventLoopWindowTarget { thread_id: DWORD, thread_msg_target: HWND, pub(crate) runner_shared: EventLoopRunnerShared, } -macro_rules! main_thread_check { - ($fn_name:literal) => {{ - let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - if thread_id != main_thread_id() { - panic!(concat!( - "Initializing the event loop outside of the main thread is a significant \ - cross-platform compatibility hazard. If you really, absolutely need to create an \ - EventLoop on a different thread, please use the `EventLoopExtWindows::", - $fn_name, - "` function." - )); - } - }}; -} - impl EventLoop { - pub fn new() -> EventLoop { - main_thread_check!("new_any_thread"); - - Self::new_any_thread() - } - - pub fn new_any_thread() -> EventLoop { - become_dpi_aware(); - Self::new_dpi_unaware_any_thread() - } - - pub fn new_dpi_unaware() -> EventLoop { - main_thread_check!("new_dpi_unaware_any_thread"); - - Self::new_dpi_unaware_any_thread() - } - - pub fn new_dpi_unaware_any_thread() -> EventLoop { + pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self { let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; + if !attributes.any_thread && thread_id != main_thread_id() { + panic!( + "Initializing the event loop outside of the main thread is a significant \ + cross-platform compatibility hazard. If you absolutely need to create an \ + EventLoop on a different thread, you can use the \ + `EventLoopBuilderExtWindows::any_thread` function." + ); + } + + if attributes.dpi_aware { + become_dpi_aware(); + } + let thread_msg_target = create_event_target_window::(); let send_thread_msg_target = thread_msg_target as usize; diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 9215a923..ad6236c6 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -2,8 +2,10 @@ use winapi::{self, shared::windef::HMENU, shared::windef::HWND}; -pub use self::{ - event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, +pub(crate) use self::{ + event_loop::{ + EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, + }, icon::WinIcon, monitor::{MonitorHandle, VideoMode}, window::Window, diff --git a/src/window.rs b/src/window.rs index 9410421d..c9c9912a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -83,7 +83,7 @@ impl WindowId { } } -/// Object that allows you to build windows. +/// Object that allows building windows. #[derive(Clone, Default)] pub struct WindowBuilder { /// The attributes to use to create the window.