Add EventLoopBuilder

This commit adds an `EventLoopBuilder` struct to simplify event loop
customization and providing options to it upon creation. It also
deprecates the use of `EventLoop::with_user_event` in favor of the same
method on new builder, and replaces old platforms specific extension
traits with the new ones on the `EventLoopBuilder`.
This commit is contained in:
Mads Marquart 2022-02-16 22:09:03 +01:00 committed by GitHub
parent 0e52672f4a
commit f3f6f1008a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 324 additions and 271 deletions

View file

@ -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`. - **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. - 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. - **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) # 0.26.1 (2022-01-05)

View file

@ -3,7 +3,7 @@ fn main() {
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoopBuilder},
window::WindowBuilder, window::WindowBuilder,
}; };
@ -13,7 +13,7 @@ fn main() {
} }
SimpleLogger::new().init().unwrap(); SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event(); let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let _window = WindowBuilder::new() let _window = WindowBuilder::new()
.with_title("A fantastic window!") .with_title("A fantastic window!")

View file

@ -10,6 +10,7 @@
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy //! [event_loop_proxy]: crate::event_loop::EventLoopProxy
//! [send_event]: crate::event_loop::EventLoopProxy::send_event //! [send_event]: crate::event_loop::EventLoopProxy::send_event
use instant::Instant; use instant::Instant;
use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::{error, fmt}; use std::{error, fmt};
@ -31,7 +32,7 @@ use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// ///
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>, pub(crate) event_loop: platform_impl::EventLoop<T>,
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`. /// Target that associates windows with an `EventLoop`.
@ -42,7 +43,62 @@ pub struct EventLoop<T: 'static> {
/// `&EventLoop`. /// `&EventLoop`.
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
pub(crate) p: platform_impl::EventLoopWindowTarget<T>, pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
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<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
pub fn new() -> Self {
Self::with_user_event()
}
}
impl<T> EventLoopBuilder<T> {
/// 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<T> {
EventLoop {
event_loop: platform_impl::EventLoop::new(&self.platform_specific),
_marker: PhantomData,
}
}
} }
impl<T> fmt::Debug for EventLoop<T> { impl<T> fmt::Debug for EventLoop<T> {
@ -119,41 +175,17 @@ impl Default for ControlFlow {
} }
impl EventLoop<()> { impl EventLoop<()> {
/// Builds a new event loop with a `()` as the user event type. /// Alias for `EventLoopBuilder::new().build()`.
/// #[inline]
/// ***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.
pub fn new() -> EventLoop<()> { pub fn new() -> EventLoop<()> {
EventLoop::<()>::with_user_event() EventLoopBuilder::new().build()
} }
} }
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
/// Builds a new event loop. #[deprecated = "Use `EventLoopBuiler::<T>::with_user_event().build()` instead."]
///
/// All caveats documented in [`EventLoop::new`] apply to this function.
///
/// ## Platform-specific
///
/// - **iOS:** Can only be called on the main thread.
pub fn with_user_event() -> EventLoop<T> { pub fn with_user_event() -> EventLoop<T> {
EventLoop { EventLoopBuilder::<T>::with_user_event().build()
event_loop: platform_impl::EventLoop::new(),
_marker: ::std::marker::PhantomData,
}
} }
/// Hijacks the calling thread and initializes the winit event loop with the provided /// Hijacks the calling thread and initializes the winit event loop with the provided

View file

@ -4,9 +4,8 @@ use std::os::raw::c_void;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
platform_impl::get_aux_state_mut,
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -74,7 +73,7 @@ impl WindowExtMacOS for Window {
} }
/// Corresponds to `NSApplicationActivationPolicy`. /// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActivationPolicy { pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`. /// Corresponds to `NSApplicationActivationPolicyRegular`.
Regular, Regular,
@ -179,36 +178,63 @@ impl WindowBuilderExtMacOS for WindowBuilder {
} }
} }
pub trait EventLoopExtMacOS { pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application. It is set to /// Sets the activation policy for the application.
/// `NSApplicationActivationPolicyRegular` by default.
/// ///
/// This function only takes effect if it's called before calling [`run`](crate::event_loop::EventLoop::run) or /// It is set to [`ActivationPolicy::Regular`] by default.
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) ///
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); /// # 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 /// # Example
/// [`run`](crate::event_loop::EventLoop::run) or ///
/// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) /// Disable creating a default menubar.
fn enable_default_menu_creation(&mut self, enable: bool); ///
/// ```
/// 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<T> EventLoopExtMacOS for EventLoop<T> {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline] #[inline]
fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
unsafe { self.platform_specific.activation_policy = activation_policy;
get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy; self
}
} }
#[inline] #[inline]
fn enable_default_menu_creation(&mut self, enable: bool) { fn with_default_menu(&mut self, enable: bool) -> &mut Self {
unsafe { self.platform_specific.default_menu = enable;
get_aux_state_mut(&**self.event_loop.delegate).create_default_menu = enable; self
}
} }
} }

View file

@ -11,7 +11,7 @@ use std::os::raw;
use std::{ptr, sync::Arc}; use std::{ptr, sync::Arc};
use crate::{ use crate::{
event_loop::{EventLoop, EventLoopWindowTarget}, event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle, monitor::MonitorHandle,
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -21,8 +21,7 @@ use crate::dpi::Size;
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection};
use crate::platform_impl::{ use crate::platform_impl::{
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow,
Window as LinuxWindow,
}; };
// TODO: stupid hack so that glutin can do its work // TODO: stupid hack so that glutin can do its work
@ -93,100 +92,42 @@ impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
} }
} }
/// Additional methods on `EventLoop` that are specific to Unix. /// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
pub trait EventLoopExtUnix { pub trait EventLoopBuilderExtUnix {
/// Builds a new `EventLoop` that is forced to use X11. /// Force using 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).
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn new_x11() -> Result<Self, XNotSupported> fn with_x11(&mut self) -> &mut Self;
where
Self: Sized;
/// Builds a new `EventLoop` that is forced to use Wayland. /// Force using 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).
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
fn new_wayland() -> Self fn with_wayland(&mut self) -> &mut Self;
where
Self: Sized;
/// 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 /// By default, the window is only allowed to be created on the main
/// that `EventLoop` be created on the main thread. /// thread, to make platform compatibility easier.
fn new_any_thread() -> Self fn with_any_thread(&mut self, any_thread: bool) -> &mut 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<Self, XNotSupported>
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;
} }
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> { impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
EventLoop {
event_loop,
_marker: std::marker::PhantomData,
}
}
impl<T> EventLoopExtUnix for EventLoop<T> {
#[inline]
fn new_any_thread() -> Self {
wrap_ev(LinuxEventLoop::new_any_thread())
}
#[inline] #[inline]
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
fn new_x11_any_thread() -> Result<Self, XNotSupported> { fn with_x11(&mut self) -> &mut Self {
LinuxEventLoop::new_x11_any_thread().map(wrap_ev) self.platform_specific.forced_backend = Some(Backend::X);
self
} }
#[inline] #[inline]
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
fn new_wayland_any_thread() -> Self { fn with_wayland(&mut self) -> &mut Self {
wrap_ev( self.platform_specific.forced_backend = Some(Backend::Wayland);
LinuxEventLoop::new_wayland_any_thread() self
// TODO: propagate
.expect("failed to open Wayland connection"),
)
} }
#[inline] #[inline]
#[cfg(feature = "x11")] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
fn new_x11() -> Result<Self, XNotSupported> { self.platform_specific.any_thread = any_thread;
LinuxEventLoop::new_x11().map(wrap_ev) self
}
#[inline]
#[cfg(feature = "wayland")]
fn new_wayland() -> Self {
wrap_ev(
LinuxEventLoop::new_wayland()
// TODO: propagate
.expect("failed to open Wayland connection"),
)
} }
} }

View file

@ -9,62 +9,61 @@ use winapi::shared::windef::{HMENU, HWND};
use crate::{ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::DeviceId, event::DeviceId,
event_loop::EventLoop, event_loop::EventLoopBuilder,
monitor::MonitorHandle, monitor::MonitorHandle,
platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon}, platform_impl::{Parent, WinIcon},
window::{BadIcon, Icon, Theme, Window, WindowBuilder}, window::{BadIcon, Icon, Theme, Window, WindowBuilder},
}; };
/// Additional methods on `EventLoop` that are specific to Windows. /// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopExtWindows { pub trait EventLoopBuilderExtWindows {
/// Creates an event loop off of the main thread. /// 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 /// # `Window` caveats
/// ///
/// Note that any `Window` created on the new thread will be destroyed when the thread /// 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 /// terminates. Attempting to use a `Window` after its parent thread terminates has
/// unspecified, although explicitly not undefined, behavior. /// unspecified, although explicitly not undefined, behavior.
fn new_any_thread() -> Self fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
where
Self: Sized;
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's /// Whether to enable process-wide DPI awareness.
/// 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.
/// ///
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here. /// By default, `winit` will attempt to enable process-wide DPI awareness. If
fn new_dpi_unaware_any_thread() -> Self /// that's undesirable, you can disable it with this function.
where ///
Self: Sized; /// # 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<T> EventLoopExtWindows for EventLoop<T> { impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
#[inline] #[inline]
fn new_any_thread() -> Self { fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
EventLoop { self.platform_specific.any_thread = any_thread;
event_loop: WindowsEventLoop::new_any_thread(), self
_marker: ::std::marker::PhantomData,
}
} }
#[inline] #[inline]
fn new_dpi_unaware() -> Self { fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
EventLoop { self.platform_specific.dpi_aware = dpi_aware;
event_loop: WindowsEventLoop::new_dpi_unaware(), self
_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,
}
} }
} }

View file

@ -71,6 +71,9 @@ pub struct EventLoop<T: 'static> {
running: bool, running: bool,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
macro_rules! call_event_handler { macro_rules! call_event_handler {
( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{
if let ControlFlow::ExitWithCode(code) = $cf { if let ControlFlow::ExitWithCode(code) = $cf {
@ -82,7 +85,7 @@ macro_rules! call_event_handler {
} }
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new() -> Self { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self {
Self { Self {
window_target: event_loop::EventLoopWindowTarget { window_target: event_loop::EventLoopWindowTarget {
p: EventLoopWindowTarget { p: EventLoopWindowTarget {

View file

@ -69,8 +69,11 @@ pub struct EventLoop<T: 'static> {
window_target: RootEventLoopWindowTarget<T>, window_target: RootEventLoopWindowTarget<T>,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
static mut SINGLETON_INIT: bool = false; static mut SINGLETON_INIT: bool = false;
unsafe { unsafe {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");

View file

@ -78,8 +78,10 @@ mod window;
use std::fmt; use std::fmt;
pub use self::{ pub(crate) use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };

View file

@ -49,6 +49,29 @@ pub mod x11;
/// If this variable is set with any other value, winit will panic. /// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; 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<Backend>,
pub(crate) any_thread: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self {
forced_backend: None,
any_thread: false,
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
@ -568,13 +591,28 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
} }
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
assert_is_main_thread("new_any_thread"); if !attributes.any_thread && !is_main_thread() {
panic!(
EventLoop::new_any_thread() "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."
);
}
#[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<T> {
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() { match env_var.as_str() {
"x11" => { "x11" => {
@ -623,26 +661,12 @@ impl<T: 'static> EventLoop<T> {
} }
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
pub fn new_wayland() -> Result<EventLoop<T>, Box<dyn Error>> { fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
assert_is_main_thread("new_wayland_any_thread");
EventLoop::new_wayland_any_thread()
}
#[cfg(feature = "wayland")]
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
wayland::EventLoop::new().map(EventLoop::Wayland) wayland::EventLoop::new().map(EventLoop::Wayland)
} }
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> { fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
assert_is_main_thread("new_x11_any_thread");
EventLoop::new_x11_any_thread()
}
#[cfg(feature = "x11")]
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
let xconn = match X11_BACKEND.lock().as_ref() { let xconn = match X11_BACKEND.lock().as_ref() {
Ok(xconn) => xconn.clone(), Ok(xconn) => xconn.clone(),
Err(err) => return Err(err.clone()), Err(err) => return Err(err.clone()),
@ -750,17 +774,6 @@ fn sticky_exit_callback<T, F>(
} }
} }
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")] #[cfg(target_os = "linux")]
fn is_main_thread() -> bool { fn is_main_thread() -> bool {
use libc::{c_long, getpid, syscall, SYS_gettid}; use libc::{c_long, getpid, syscall, SYS_gettid};

View file

@ -13,12 +13,8 @@ use std::{
static AUX_DELEGATE_STATE_NAME: &str = "auxState"; static AUX_DELEGATE_STATE_NAME: &str = "auxState";
pub struct AuxDelegateState { 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 activation_policy: ActivationPolicy,
pub default_menu: bool,
pub create_default_menu: bool,
} }
pub struct AppDelegateClass(pub *const Class); pub struct AppDelegateClass(pub *const Class);
@ -54,11 +50,12 @@ extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe { unsafe {
let this: id = msg_send![class, alloc]; let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init]; let this: id = msg_send![this, init];
// TODO: Remove the need for this initialization here
(*this).set_ivar( (*this).set_ivar(
AUX_DELEGATE_STATE_NAME, AUX_DELEGATE_STATE_NAME,
Box::into_raw(Box::new(RefCell::new(AuxDelegateState { Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular, activation_policy: ActivationPolicy::Regular,
create_default_menu: true, default_menu: true,
}))) as *mut c_void, }))) as *mut c_void,
); );
this this

View file

@ -296,7 +296,7 @@ impl AppState {
}; };
HANDLER.set_ready(); HANDLER.set_ready();
HANDLER.waker().start(); 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 { if create_default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow // The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created // 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(); let ns_app = NSApp();
// We need to delay setting the activation policy and activating the app // We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the // 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; let act_pol = get_aux_state_mut(app_delegate).activation_policy;
ns_app.setActivationPolicy_(match act_pol { ns_app.setActivationPolicy_(match act_pol {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,

View file

@ -22,7 +22,10 @@ use crate::{
event::Event, event::Event,
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{ platform::macos::ActivationPolicy,
platform_impl::{
get_aux_state_mut,
platform::{
app::APP_CLASS, app::APP_CLASS,
app_delegate::APP_DELEGATE_CLASS, app_delegate::APP_DELEGATE_CLASS,
app_state::{AppState, Callback}, app_state::{AppState, Callback},
@ -30,6 +33,7 @@ use crate::{
observer::*, observer::*,
util::IdRef, util::IdRef,
}, },
},
}; };
#[derive(Default)] #[derive(Default)]
@ -100,7 +104,9 @@ impl<T> EventLoopWindowTarget<T> {
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
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<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>, panic_info: Rc<PanicInfo>,
@ -114,8 +120,23 @@ pub struct EventLoop<T: 'static> {
_callback: Option<Rc<Callback<T>>>, _callback: Option<Rc<Callback<T>>>,
} }
#[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<T> EventLoop<T> { impl<T> EventLoop<T> {
pub fn new() -> Self { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
let delegate = unsafe { let delegate = unsafe {
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread); let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
if is_main_thread == NO { if is_main_thread == NO {
@ -129,15 +150,21 @@ impl<T> EventLoop<T> {
let app: id = msg_send![APP_CLASS.0, sharedApplication]; let app: id = msg_send![APP_CLASS.0, sharedApplication];
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); 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(|| { autoreleasepool(|| {
let _: () = msg_send![app, setDelegate:*delegate]; let _: () = msg_send![app, setDelegate:*delegate];
}); });
delegate delegate
}; };
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));
EventLoop { EventLoop {
delegate, _delegate: delegate,
window_target: Rc::new(RootWindowTarget { window_target: Rc::new(RootWindowTarget {
p: Default::default(), p: Default::default(),
_marker: PhantomData, _marker: PhantomData,

View file

@ -18,9 +18,12 @@ mod window_delegate;
use std::{fmt, ops::Deref, sync::Arc}; use std::{fmt, ops::Deref, sync::Arc};
pub use self::{ pub(crate) use self::{
app_delegate::{get_aux_state_mut, AuxDelegateState}, app_delegate::get_aux_state_mut,
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, event_loop::{
EventLoop, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
Proxy as EventLoopProxy,
},
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
}; };

View file

@ -16,8 +16,11 @@ pub struct EventLoop<T: 'static> {
elw: root::EventLoopWindowTarget<T>, elw: root::EventLoopWindowTarget<T>,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T> EventLoop<T> { impl<T> EventLoop<T> {
pub fn new() -> Self { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self {
EventLoop { EventLoop {
elw: root::EventLoopWindowTarget { elw: root::EventLoopWindowTarget {
p: WindowTarget::new(), p: WindowTarget::new(),

View file

@ -28,8 +28,9 @@ mod backend;
pub use self::device::Id as DeviceId; pub use self::device::Id as DeviceId;
pub use self::error::OsError; pub use self::error::OsError;
pub use self::event_loop::{ pub(crate) use self::event_loop::{
EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget, EventLoop, PlatformSpecificEventLoopAttributes, Proxy as EventLoopProxy,
WindowTarget as EventLoopWindowTarget,
}; };
pub use self::monitor::{Handle as MonitorHandle, Mode as VideoMode}; pub use self::monitor::{Handle as MonitorHandle, Mode as VideoMode};
pub use self::window::{ pub use self::window::{

View file

@ -113,48 +113,44 @@ pub struct EventLoop<T: 'static> {
window_target: RootELW<T>, window_target: RootELW<T>,
} }
#[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<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
thread_id: DWORD, thread_id: DWORD,
thread_msg_target: HWND, thread_msg_target: HWND,
pub(crate) runner_shared: EventLoopRunnerShared<T>, pub(crate) runner_shared: EventLoopRunnerShared<T>,
} }
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<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn new() -> EventLoop<T> { pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
main_thread_check!("new_any_thread");
Self::new_any_thread()
}
pub fn new_any_thread() -> EventLoop<T> {
become_dpi_aware();
Self::new_dpi_unaware_any_thread()
}
pub fn new_dpi_unaware() -> EventLoop<T> {
main_thread_check!("new_dpi_unaware_any_thread");
Self::new_dpi_unaware_any_thread()
}
pub fn new_dpi_unaware_any_thread() -> EventLoop<T> {
let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; 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::<T>(); let thread_msg_target = create_event_target_window::<T>();
let send_thread_msg_target = thread_msg_target as usize; let send_thread_msg_target = thread_msg_target as usize;

View file

@ -2,8 +2,10 @@
use winapi::{self, shared::windef::HMENU, shared::windef::HWND}; use winapi::{self, shared::windef::HMENU, shared::windef::HWND};
pub use self::{ pub(crate) use self::{
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, event_loop::{
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
},
icon::WinIcon, icon::WinIcon,
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
window::Window, window::Window,

View file

@ -83,7 +83,7 @@ impl WindowId {
} }
} }
/// Object that allows you to build windows. /// Object that allows building windows.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct WindowBuilder { pub struct WindowBuilder {
/// The attributes to use to create the window. /// The attributes to use to create the window.