From 28023d9f5bb74b66a9b294b4c8fef6c46a9d9b92 Mon Sep 17 00:00:00 2001 From: Matthew Russo Date: Wed, 25 Mar 2020 22:38:25 -0700 Subject: [PATCH 01/56] upgrades x11-dl to 2.18.5 to fix #376 (#1517) x11-dl was using std::mem::uninitialized incorrectly and when rustlang added MaybeUninit and intrinsic panics on UB caused by improper use of uninitialized (see rust-lang/rust/pull/69922) it caused issues with X11 initialization. x11-dl pr erlepereira/x11-rs/pull/101 updated x11-dl to use MaybeUninit correctly --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 71fb66f792..4cc88d5f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", " mio = "0.6" mio-extras = "2.0" smithay-client-toolkit = "^0.6.6" -x11-dl = "2.18.3" +x11-dl = "2.18.5" percent-encoding = "2.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] From 0bc58f695b01977f81d1259745c89093d18bb24a Mon Sep 17 00:00:00 2001 From: Murarth Date: Fri, 10 Apr 2020 11:29:33 -0700 Subject: [PATCH 02/56] Fix warnings (#1530) --- src/platform_impl/linux/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 8958ddc030..4713038f38 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -63,7 +63,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { lazy_static! { pub static ref X11_BACKEND: Mutex, XNotSupported>> = - { Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) }; + Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); } #[derive(Debug, Clone)] From a8e777a5dffac9d8528166d44a4cafddffe21e37 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sat, 11 Apr 2020 15:20:38 -0400 Subject: [PATCH 03/56] Fix a possible double-borrow during event handling (#1512) --- CHANGELOG.md | 1 + src/platform_impl/web/event_loop/runner.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a233ac42..0d198d7de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On X11, fix `ResumeTimeReached` being fired too early. +- On Web, fix a possible panic during event handling # 0.22.0 (2020-03-09) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 398fcaf6cb..7c67eac049 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -202,7 +202,6 @@ impl Shared { // It should only ever be called from send_event fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) { let is_closed = self.is_closed(); - match *self.0.runner.borrow_mut() { Some(ref mut runner) => { // An event is being processed, so the runner should be marked busy @@ -227,7 +226,9 @@ impl Shared { // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().is_some() { // Take an event out of the queue and handle it - if let Some(event) = self.0.events.borrow_mut().pop_front() { + // Make sure not to let the borrow_mut live during the next handle_event + let event = { self.0.events.borrow_mut().pop_front() }; + if let Some(event) = event { self.handle_event(event, control); } } From 1f24a09570d3bf0a39af836c3a7c212a0b65ff70 Mon Sep 17 00:00:00 2001 From: Jurgis Date: Sat, 11 Apr 2020 22:49:07 +0300 Subject: [PATCH 04/56] Implement `requestAnimationFrame` for web (#1519) * Use requestAnimationFrame for polling wasm * Implement `requestAnimationFrame` for stdweb Co-authored-by: Ryan G --- CHANGELOG.md | 1 + src/event_loop.rs | 3 +- src/platform_impl/web/event_loop/runner.rs | 2 +- src/platform_impl/web/event_loop/state.rs | 2 +- src/platform_impl/web/stdweb/mod.rs | 2 +- src/platform_impl/web/stdweb/timeout.rs | 40 +++++++++++++++++- src/platform_impl/web/web_sys/mod.rs | 2 +- src/platform_impl/web/web_sys/timeout.rs | 47 ++++++++++++++++++++++ 8 files changed, 93 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d198d7de1..8af1139dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On X11, fix `ResumeTimeReached` being fired too early. +- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling # 0.22.0 (2020-03-09) diff --git a/src/event_loop.rs b/src/event_loop.rs index 8a05e31359..224292a564 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -72,7 +72,8 @@ impl fmt::Debug for EventLoopWindowTarget { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. + /// whether or not new events are available to process. For web, events are sent when + /// `requestAnimationFrame` fires. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 7c67eac049..d381b942b2 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -241,7 +241,7 @@ impl Shared { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), + request: backend::AnimationFrameRequest::new(move || cloned.poll()), } } root::ControlFlow::Wait => State::Wait { diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs index 23e8045fc3..16b6e6232c 100644 --- a/src/platform_impl/web/event_loop/state.rs +++ b/src/platform_impl/web/event_loop/state.rs @@ -15,7 +15,7 @@ pub enum State { start: Instant, }, Poll { - timeout: backend::Timeout, + request: backend::AnimationFrameRequest, }, Exit, } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index a95dfc7d36..3632307ca7 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtStdweb; diff --git a/src/platform_impl/web/stdweb/timeout.rs b/src/platform_impl/web/stdweb/timeout.rs index 00ac2ab02d..72bfb72bb1 100644 --- a/src/platform_impl/web/stdweb/timeout.rs +++ b/src/platform_impl/web/stdweb/timeout.rs @@ -1,5 +1,7 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; -use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; +use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle}; #[derive(Debug)] pub struct Timeout { @@ -23,3 +25,39 @@ impl Drop for Timeout { handle.clear(); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: Option, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let handle = window().request_animation_frame(move |_| { + (*c_fired).set(true); + f(); + }); + + AnimationFrameRequest { + handle: Some(handle), + fired, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + if let Some(handle) = self.handle.take() { + handle.cancel(); + } + } + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index c41cd069f2..94efe1ec27 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtWebSys; diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index e7ce69a083..e95c54ed51 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; @@ -38,3 +40,48 @@ impl Drop for Timeout { window.clear_timeout_with_handle(self.handle); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: i32, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, + _closure: Closure, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let window = web_sys::window().expect("Failed to obtain window"); + + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let closure = Closure::wrap(Box::new(move || { + (*c_fired).set(true); + f(); + }) as Box); + + let handle = window + .request_animation_frame(&closure.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + + AnimationFrameRequest { + handle, + fired, + _closure: closure, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + let window = web_sys::window().expect("Failed to obtain window"); + window + .cancel_animation_frame(self.handle) + .expect("Failed to cancel animation frame"); + } + } +} From d5609729cc5c23a1d74c8c25d3af9c3bc7512a6f Mon Sep 17 00:00:00 2001 From: Ryan G Date: Fri, 17 Apr 2020 13:36:42 -0400 Subject: [PATCH 05/56] Bump version to 0.22.1 (#1537) There are a few relatively important bugfixes with no API impact in the master branch. We might as well release this as a non-breaking change. --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af1139dbc..ecb4aa4f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.22.1 (2020-04-16) + - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling diff --git a/Cargo.toml b/Cargo.toml index 4cc88d5f1b..62d46bf0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.22.0" +version = "0.22.1" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index f6e69a7539..3937622557 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.22.0" +winit = "0.22.1" ``` ## [Documentation](https://docs.rs/winit) From 4c4d0916fd736fcf28d5ee0f80a0fb367de4a0d8 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 19 Apr 2020 19:55:10 +0200 Subject: [PATCH 06/56] control_flow example: fix wait_cancelled logic again (#1511) --- examples/control_flow.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 13b5bcf832..6d71541216 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -41,15 +41,9 @@ fn main() { println!("{:?}", event); match event { Event::NewEvents(start_cause) => { - wait_cancelled = mode == Mode::WaitUntil; - match start_cause { - StartCause::ResumeTimeReached { - start: _, - requested_resume: _, - } => { - wait_cancelled = false; - } - _ => (), + wait_cancelled = match start_cause { + StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, + _ => false, } } Event::WindowEvent { event, .. } => match event { From 6dae994bb4c2d73d5a9f31718feb41e1fa2b98ff Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 19 Apr 2020 11:58:58 -0700 Subject: [PATCH 07/56] Mention raw-window-handle in library docs (#1528) --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6011203e0b..cf43f3964b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,8 +94,9 @@ //! # Drawing on the window //! //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to -//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you -//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. +//! retrieve the raw handle of the window (see the [`platform`] module and/or the +//! [`raw_window_handle`] method), which in turn allows you to create an +//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return @@ -116,6 +117,7 @@ //! [`UserEvent`]: event::Event::UserEvent //! [`LoopDestroyed`]: event::Event::LoopDestroyed //! [`platform`]: platform +//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle #![deny(rust_2018_idioms)] #![deny(intra_doc_link_resolution_failure)] From 78a62ec5473e9cd5472c18f859103b0b0cb88362 Mon Sep 17 00:00:00 2001 From: simlay Date: Sun, 19 Apr 2020 12:37:13 -0700 Subject: [PATCH 08/56] Added more docs.rs targets (#1521) --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 62d46bf0ed..0b6f833f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,9 @@ documentation = "https://docs.rs/winit" categories = ["gui"] [package.metadata.docs.rs] -features = ["serde"] +features = ["serde", "web-sys"] +default-target = "x86_64-unknown-linux-gnu" +targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] [features] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] From aabe42d252f66c6410b2f805a9ea80f12517e16d Mon Sep 17 00:00:00 2001 From: Yanchi Toth Date: Sun, 19 Apr 2020 21:52:48 +0200 Subject: [PATCH 09/56] Preserve with_maximized on windows (#1515) --- CHANGELOG.md | 2 ++ src/platform_impl/windows/window.rs | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb4aa4f7c..e428fbbee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Windows, fix `WindowBuilder::with_maximized` being ignored. + # 0.22.1 (2020-04-16) - On X11, fix `ResumeTimeReached` being fired too early. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 3c58e1fd0b..45a3b5debd 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -728,8 +728,6 @@ unsafe fn init( } } - window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); - // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). @@ -757,6 +755,11 @@ unsafe fn init( .inner_size .unwrap_or_else(|| PhysicalSize::new(1024, 768).into()); win.set_inner_size(dimensions); + if attributes.maximized { + // Need to set MAXIMIZED after setting `inner_size` as + // `Window::set_inner_size` changes MAXIMIZED to false. + win.set_maximized(true); + } win.set_visible(attributes.visible); if let Some(_) = attributes.fullscreen { From 849b8f5dce4d3f93b251259f5323ea9fd43e0c05 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 19 Apr 2020 14:09:08 -0700 Subject: [PATCH 10/56] Clarify when RedrawRequested is useful (#1529) Co-Authored-By: Osspial --- src/event.rs | 10 +++++++--- src/lib.rs | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/event.rs b/src/event.rs index 4756f3d895..6b9fe1a630 100644 --- a/src/event.rs +++ b/src/event.rs @@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> { /// /// This event is useful as a place to put your code that should be run after all /// state-changing events have been handled and you want to do stuff (updating state, performing - /// calculations, etc) that happens as the "main body" of your event loop. If your program draws - /// graphics, it's usually better to do it in response to + /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws + /// graphics when something changes, it's usually better to do it in response to /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted - /// immediately after this event. + /// immediately after this event. Programs that draw graphics continuously, like most games, + /// can render here unconditionally for simplicity. MainEventsCleared, /// Emitted after `MainEventsCleared` when a window should be redrawn. @@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> { /// /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests /// into a single event, to help avoid duplicating rendering work. + /// + /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless + /// something changes, like most non-game GUIs. RedrawRequested(WindowId), /// Emitted after all `RedrawRequested` events have been processed and control flow is about to diff --git a/src/lib.rs b/src/lib.rs index cf43f3964b..e3b0be75d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,14 +73,18 @@ //! // Application update code. //! //! // Queue a RedrawRequested event. +//! // +//! // You only need to call this if you've determined that you need to redraw, in +//! // applications which do not always need to. Applications that redraw continuously +//! // can just render here instead. //! window.request_redraw(); //! }, //! Event::RedrawRequested(_) => { //! // Redraw the application. //! // -//! // It's preferrable to render in this event rather than in MainEventsCleared, since -//! // rendering in here allows the program to gracefully handle redraws requested -//! // by the OS. +//! // It's preferable for applications that do not render continuously to render in +//! // this event rather than in MainEventsCleared, since rendering in here allows +//! // the program to gracefully handle redraws requested by the OS. //! }, //! _ => () //! } From 47ff8d61d1330372bd04cc398827e2063c395dd3 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 20 Apr 2020 00:04:30 -0400 Subject: [PATCH 11/56] Document that platforms will display garbage data in the window by default (#1541) --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e3b0be75d3..ce66665616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,12 @@ //! [`raw_window_handle`] method), which in turn allows you to create an //! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! +//! Note that many platforms will display garbage data in the window's client area if the +//! application doesn't render anything to the window by the time the desktop compositor is ready to +//! display the window to the user. If you notice this happening, you should create the window with +//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the +//! window visible only once you're ready to render into it. +//! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return //! [`EventLoop::new()`]: event_loop::EventLoop::new From 54bc41f68bc852fd220840dd4aa3c5f4369b70ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Mon, 20 Apr 2020 23:48:42 +0200 Subject: [PATCH 12/56] Implement `Drop` for `Proxy` on macOS platform (#1526) --- CHANGELOG.md | 1 + src/platform_impl/macos/event_loop.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e428fbbee1..ef5e9fd955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling +- On macOS, fix `EventLoopProxy` leaking memory for every instance. # 0.22.0 (2020-03-09) diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index cc3d392840..01437b845e 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -117,6 +117,14 @@ pub struct Proxy { unsafe impl Send for Proxy {} +impl Drop for Proxy { + fn drop(&mut self) { + unsafe { + CFRelease(self.source as _); + } + } +} + impl Clone for Proxy { fn clone(&self) -> Self { Proxy::new(self.sender.clone()) From 114fe9d502cd4b37ee332f4165fb92c1056e25d9 Mon Sep 17 00:00:00 2001 From: Matthias Fauconneau Date: Wed, 22 Apr 2020 18:00:41 +0200 Subject: [PATCH 13/56] wayland: rework scale factor handling (#1538) - Always send Resized events in case of scale factor change - Properly take into account the resize the user can do using the resize event. --- src/platform_impl/linux/wayland/event_loop.rs | 85 ++++++++++--------- src/platform_impl/linux/wayland/window.rs | 83 +++++++++--------- 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 47c5aea634..5a3614c8b5 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -714,8 +714,43 @@ impl EventLoop { window_target.store.lock().unwrap().for_each(|window| { let window_id = crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); - if let Some(frame) = window.frame { - if let Some((w, h)) = window.newsize { + + // Update window logical .size field (for callbacks using .inner_size) + let (old_logical_size, mut logical_size) = { + let mut window_size = window.size.lock().unwrap(); + let old_logical_size = *window_size; + *window_size = window.new_size.unwrap_or(old_logical_size); + (old_logical_size, *window_size) + }; + + if let Some(scale_factor) = window.new_scale_factor { + // Update cursor scale factor + self.cursor_manager + .lock() + .unwrap() + .update_scale_factor(scale_factor as u32); + let new_logical_size = { + let scale_factor = scale_factor as f64; + let mut physical_size = + LogicalSize::::from(logical_size).to_physical(scale_factor); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }); + physical_size.to_logical::(scale_factor).into() + }; + // Update size if changed by callback + if new_logical_size != logical_size { + logical_size = new_logical_size; + *window.size.lock().unwrap() = logical_size.into(); + } + } + + if window.new_size.is_some() || window.new_scale_factor.is_some() { + if let Some(frame) = window.frame { // Update decorations state match window.decorations_action { Some(DecorationsAction::Hide) => frame.set_decorate(false), @@ -726,51 +761,23 @@ impl EventLoop { // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case // it overlaps mutter's `bounding box`, so we can't avoid this resize call, // which calls `set_geometry` under the hood, for now. + let (w, h) = logical_size; frame.resize(w, h); frame.refresh(); - - // Don't send resize event downstream if the new size is identical to the - // current one. - if (w, h) != *window.size { - let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); - let physical_size = logical_size - .to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Resized(physical_size), - }); - *window.size = (w, h); - } } - - if let Some(dpi) = window.new_dpi { - // Update cursor scale factor - { - self.cursor_manager - .lock() - .unwrap() - .update_scale_factor(dpi as u32); - }; - let dpi = dpi as f64; - let logical_size = LogicalSize::::from(*window.size); - let mut new_inner_size = logical_size.to_physical(dpi); - + // Don't send resize event downstream if the new logical size and scale is identical to the + // current one + if logical_size != old_logical_size || window.new_scale_factor.is_some() { + let physical_size = LogicalSize::::from(logical_size).to_physical( + window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64, + ); callback(Event::WindowEvent { window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: dpi, - new_inner_size: &mut new_inner_size, - }, + event: WindowEvent::Resized(physical_size), }); - - let (w, h) = new_inner_size.to_logical::(dpi).into(); - frame.resize(w, h); - // Refresh frame to rescale decorations - frame.refresh(); - *window.size = (w, h); } } + if window.closed { callback(Event::WindowEvent { window_id, diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 7c30a79bf5..12ae9ba1d5 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -59,15 +59,20 @@ impl Window { // Create the surface first to get initial DPI let window_store = evlp.store.clone(); let cursor_manager = evlp.cursor_manager.clone(); - let surface = evlp.env.create_surface(move |dpi, surface| { - window_store.lock().unwrap().dpi_change(&surface, dpi); - surface.set_buffer_scale(dpi); + let surface = evlp.env.create_surface(move |scale_factor, surface| { + window_store + .lock() + .unwrap() + .scale_factor_change(&surface, scale_factor); + surface.set_buffer_scale(scale_factor); }); - let dpi = get_dpi_factor(&surface) as f64; + // Always 1. + let scale_factor = get_dpi_factor(&surface); + let (width, height) = attributes .inner_size - .map(|size| size.to_logical::(dpi).into()) + .map(|size| size.to_logical::(scale_factor as f64).into()) .unwrap_or((800, 600)); // Create the window @@ -91,7 +96,7 @@ impl Window { for window in &mut store.windows { if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.newsize = new_size; + window.new_size = new_size; *(window.need_refresh.lock().unwrap()) = true; { // Get whether we're in fullscreen @@ -173,12 +178,12 @@ impl Window { frame.set_min_size( attributes .min_inner_size - .map(|size| size.to_logical::(dpi).into()), + .map(|size| size.to_logical::(scale_factor as f64).into()), ); frame.set_max_size( attributes .max_inner_size - .map(|size| size.to_logical::(dpi).into()), + .map(|size| size.to_logical::(scale_factor as f64).into()), ); let kill_switch = Arc::new(Mutex::new(false)); @@ -189,7 +194,7 @@ impl Window { evlp.store.lock().unwrap().windows.push(InternalWindow { closed: false, - newsize: None, + new_size: None, size: size.clone(), need_refresh: need_refresh.clone(), fullscreen: fullscreen.clone(), @@ -198,8 +203,8 @@ impl Window { surface: surface.clone(), kill_switch: kill_switch.clone(), frame: Arc::downgrade(&frame), - current_dpi: 1, - new_dpi: None, + current_scale_factor: scale_factor, + new_scale_factor: None, decorated: decorated.clone(), pending_decorations_action: pending_decorations_action.clone(), }); @@ -250,9 +255,9 @@ impl Window { } pub fn inner_size(&self) -> PhysicalSize { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; let size = LogicalSize::::from(*self.size.lock().unwrap()); - size.to_physical(dpi) + size.to_physical(scale_factor) } pub fn request_redraw(&self) { @@ -261,38 +266,38 @@ impl Window { #[inline] pub fn outer_size(&self) -> PhysicalSize { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); let size = LogicalSize::::from((w, h)); - size.to_physical(dpi) + size.to_physical(scale_factor) } #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user pub fn set_inner_size(&self, size: Size) { - let dpi = self.scale_factor() as f64; - let (w, h) = size.to_logical::(dpi).into(); + let scale_factor = self.scale_factor() as f64; + let (w, h) = size.to_logical::(scale_factor).into(); self.frame.lock().unwrap().resize(w, h); *(self.size.lock().unwrap()) = (w, h); } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; self.frame .lock() .unwrap() - .set_min_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); + .set_min_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; self.frame .lock() .unwrap() - .set_max_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); + .set_max_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); } #[inline] @@ -428,7 +433,7 @@ impl Drop for Window { struct InternalWindow { surface: wl_surface::WlSurface, // TODO: CONVERT TO LogicalSizes - newsize: Option<(u32, u32)>, + new_size: Option<(u32, u32)>, size: Arc>, need_refresh: Arc>, fullscreen: Arc>, @@ -437,8 +442,8 @@ struct InternalWindow { closed: bool, kill_switch: Arc>, frame: Weak>>, - current_dpi: i32, - new_dpi: Option, + current_scale_factor: i32, + new_scale_factor: Option, decorated: Arc>, pending_decorations_action: Arc>>, } @@ -448,10 +453,10 @@ pub struct WindowStore { } pub struct WindowStoreForEach<'a> { - pub newsize: Option<(u32, u32)>, - pub size: &'a mut (u32, u32), - pub prev_dpi: i32, - pub new_dpi: Option, + pub new_size: Option<(u32, u32)>, + pub size: &'a Mutex<(u32, u32)>, + pub prev_scale_factor: i32, + pub new_scale_factor: Option, pub closed: bool, pub grab_cursor: Option, pub surface: &'a wl_surface::WlSurface, @@ -499,10 +504,11 @@ impl WindowStore { } } - fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { + fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { for window in &mut self.windows { if surface.as_ref().equals(&window.surface.as_ref()) { - window.new_dpi = Some(new); + window.new_scale_factor = Some(new); + *(window.need_refresh.lock().unwrap()) = true; } } } @@ -512,15 +518,18 @@ impl WindowStore { F: FnMut(WindowStoreForEach<'_>), { for window in &mut self.windows { + let prev_scale_factor = window.current_scale_factor; + if let Some(scale_factor) = window.new_scale_factor { + window.current_scale_factor = scale_factor; + } let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); - let mut size = { *window.size.lock().unwrap() }; let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; f(WindowStoreForEach { - newsize: window.newsize.take(), - size: &mut size, - prev_dpi: window.current_dpi, - new_dpi: window.new_dpi, + new_size: window.new_size.take(), + size: &window.size, + prev_scale_factor, + new_scale_factor: window.new_scale_factor.take(), closed: window.closed, grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), surface: &window.surface, @@ -528,10 +537,6 @@ impl WindowStore { frame: opt_mutex_lock.as_mut().map(|m| &mut **m), decorations_action, }); - *window.size.lock().unwrap() = size; - if let Some(dpi) = window.new_dpi.take() { - window.current_dpi = dpi; - } // avoid re-spamming the event window.closed = false; } From 26775fa0b621c225753cefb2a3babead3be33f46 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Apr 2020 20:42:45 +0000 Subject: [PATCH 14/56] Report mouse motion before click (#1490) * Report mouse motion before click This fixes an issue on macOS where a mouse click would be generated, without ever getting a mouse motion to the position before the click. This leads to the application thinking the mouse click occurred at a position other than the actual mouse location. This happens due to mouse motion above the window not automatically giving focus to the window, unless it is actually clicked, making it possible to move the window without motion events. Fixes #942. * Add additional mouse motion events Co-authored-by: Ryan Goldstein --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5e9fd955..dc79991608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. +- On macOS, a mouse motion event is now generated before every mouse click. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 839a2bc023..82ee50767e 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -869,26 +869,32 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem } extern "C" fn mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Left, ElementState::Pressed); } extern "C" fn mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Left, ElementState::Released); } extern "C" fn right_mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Right, ElementState::Pressed); } extern "C" fn right_mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Right, ElementState::Released); } extern "C" fn other_mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Middle, ElementState::Pressed); } extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Middle, ElementState::Released); } @@ -986,6 +992,9 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) { extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { trace!("Triggered `scrollWheel`"); + + mouse_motion(this, event); + unsafe { let delta = { let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); @@ -1031,6 +1040,9 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { trace!("Triggered `pressureChangeWithEvent`"); + + mouse_motion(this, event); + unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); From 9975ced049381f6651d06871239fc0899e421638 Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Mon, 27 Apr 2020 00:47:08 -0700 Subject: [PATCH 15/56] update macos libs --- Cargo.toml | 8 ++++---- src/platform/macos.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4dc41ed561..8e51e60195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,14 +38,14 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.19.1" -core-foundation = "0.6" -core-graphics = "0.17.3" +cocoa = ">=0.20" +core-foundation = ">=0.7.0" +core-graphics = ">=0.19" dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] -version = "0.1.3" +git = "https://github.com/LuoZijun/rust-core-video-sys" default_features = false features = ["display_link"] diff --git a/src/platform/macos.rs b/src/platform/macos.rs index b1a298b365..b275c7538d 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,7 +4,7 @@ use std::os::raw::c_void; use crate::{ dpi::LogicalSize, - event_loop::{EventLoop, EventLoopWindowTarget}, + event_loop::EventLoopWindowTarget, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; From 36ab8909b9a2efee06fc1f8af50b5987d7274f80 Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Fri, 1 May 2020 21:04:03 -0700 Subject: [PATCH 16/56] modify dependency --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e51e60195..a24df7eebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,9 +38,9 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = ">=0.20" -core-foundation = ">=0.7.0" -core-graphics = ">=0.19" +cocoa = "0.20" +core-foundation = "0.7" +core-graphics = "0.19" dispatch = "0.2.0" objc = "0.2.6" From f3b49929b4be74f476af452fc764bf8b36bfc10d Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Sat, 2 May 2020 00:45:50 -0700 Subject: [PATCH 17/56] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd299bdc32..578c10d557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. +- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. From b4c6cdf9a33530a2116f37a21547341c34537be1 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 4 May 2020 15:14:13 -0400 Subject: [PATCH 18/56] Fix several crashes on Windows by heavily simplifying the event loop code (#1496) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 492 ++++++++---- .../windows/event_loop/runner.rs | 705 ++++++++---------- src/platform_impl/windows/window_state.rs | 4 +- 4 files changed, 658 insertions(+), 544 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc79991608..f9259fd55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. # 0.22.1 (2020-04-16) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index e5a4d4fce8..a7deac50c5 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1,17 +1,4 @@ #![allow(non_snake_case)] -//! An events loop on Win32 is a background thread. -//! -//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. -//! Destroying the events loop stops the thread. -//! -//! You can use the `execute_in_thread` method to execute some code in the background thread. -//! Since Win32 requires you to create a window in the right thread, you must use this method -//! to create a window. -//! -//! If you create a window whose class is set to `callback`, the window's events will be -//! propagated with `run_forever` and `poll_events`. -//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to -//! add a `WindowState` entry to a list of window to be used by the callback. mod runner; @@ -24,6 +11,7 @@ use std::{ mpsc::{self, Receiver, Sender}, Arc, }, + thread, time::{Duration, Instant}, }; use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR}; @@ -41,7 +29,6 @@ use winapi::{ }, }; -use self::runner::{ELRShared, EventLoopRunnerShared}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, @@ -57,6 +44,7 @@ use crate::{ }, window::{Fullscreen, WindowId as RootWindowId}, }; +use runner::{EventLoopRunner, EventLoopRunnerShared}; type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: UINT, @@ -160,9 +148,17 @@ impl EventLoop { pub fn new_dpi_unaware_any_thread() -> EventLoop { let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - let runner_shared = Rc::new(ELRShared::new()); - let (thread_msg_target, thread_msg_sender) = - thread_event_target_window(runner_shared.clone()); + + let thread_msg_target = create_event_target_window(); + + let send_thread_msg_target = thread_msg_target as usize; + thread::spawn(move || wait_thread(thread_id, send_thread_msg_target as HWND)); + let wait_thread_id = get_wait_thread_id(); + + let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + + let thread_msg_sender = + subclass_event_target_window(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); EventLoop { @@ -200,87 +196,39 @@ impl EventLoop { self.window_target .p .runner_shared - .set_runner(self, move |event, control_flow| { + .set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) - }) + }); } let runner = &self.window_target.p.runner_shared; unsafe { let mut msg = mem::zeroed(); - let mut unread_message_exists = false; + runner.poll(); 'main: loop { - if let Err(payload) = runner.take_panic_error() { - runner.destroy_runner(); - panic::resume_unwind(payload); + if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { + break 'main; } + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); - runner.new_events(); - loop { - if !unread_message_exists { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - 0, - 0, - winuser::PM_REMOVE, - ) { - break; - } - } - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - - unread_message_exists = false; - - if msg.message == winuser::WM_PAINT { - // An "external" redraw was requested. - // Note that the WM_PAINT has been dispatched and - // has caused the event loop to emit the MainEventsCleared event. - // See EventLoopRunner::process_event(). - // The call to main_events_cleared() below will do nothing. - break; - } + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); } - // Make sure we emit the MainEventsCleared event if no WM_PAINT message was received. - runner.main_events_cleared(); - // Drain eventual WM_PAINT messages sent if user called request_redraw() - // during handling of MainEventsCleared. - loop { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - winuser::PM_QS_PAINT | winuser::PM_REMOVE, - ) { - break; - } - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } - runner.redraw_events_cleared(); - match runner.control_flow() { - ControlFlow::Exit => break 'main, - ControlFlow::Wait => { - if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { - break 'main; - } - unread_message_exists = true; - } - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - } - ControlFlow::Poll => (), + if runner.control_flow() == ControlFlow::Exit && !runner.handling_events() { + break 'main; } } } - runner.destroy_loop(); - runner.destroy_runner(); + unsafe { + runner.call_event_handler(Event::LoopDestroyed); + } + runner.reset_runner(); } pub fn create_proxy(&self) -> EventLoopProxy { @@ -316,24 +264,83 @@ fn main_thread_id() -> DWORD { unsafe { MAIN_THREAD_ID } } -unsafe fn wait_until_time_or_msg(wait_until: Instant) { - let now = Instant::now(); - if now < wait_until { - // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond - // from the requested time and spinlock for the remainder to compensate for that. - let resume_reason = winuser::MsgWaitForMultipleObjectsEx( +fn get_wait_thread_id() -> DWORD { + unsafe { + let mut msg = mem::zeroed(); + let result = winuser::GetMessageW( + &mut msg, + -1 as _, + *SEND_WAIT_THREAD_ID_MSG_ID, + *SEND_WAIT_THREAD_ID_MSG_ID, + ); + assert_eq!( + msg.message, *SEND_WAIT_THREAD_ID_MSG_ID, + "this shouldn't be possible. please open an issue with Winit. error code: {}", + result + ); + msg.lParam as DWORD + } +} + +fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) { + unsafe { + let mut msg: winuser::MSG; + + let cur_thread_id = processthreadsapi::GetCurrentThreadId(); + winuser::PostThreadMessageW( + parent_thread_id, + *SEND_WAIT_THREAD_ID_MSG_ID, 0, - ptr::null(), - dur2timeout(wait_until - now).saturating_sub(1), - winuser::QS_ALLEVENTS, - winuser::MWMO_INPUTAVAILABLE, + cur_thread_id as LPARAM, ); - if resume_reason == winerror::WAIT_TIMEOUT { - let mut msg = mem::zeroed(); - while Instant::now() < wait_until { - if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { - break; + let mut wait_until_opt = None; + 'main: loop { + // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get + // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't + // additional messages to process. + msg = mem::zeroed(); + + if wait_until_opt.is_some() { + if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, winuser::PM_REMOVE) { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } else { + if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { + break 'main; + } else { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } + + if msg.message == *WAIT_UNTIL_MSG_ID { + wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _)); + } else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID { + wait_until_opt = None; + } + + if let Some(wait_until) = wait_until_opt { + let now = Instant::now(); + if now < wait_until { + // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract + // 1 millisecond from the requested time and spinlock for the remainder to + // compensate for that. + let resume_reason = winuser::MsgWaitForMultipleObjectsEx( + 0, + ptr::null(), + dur2timeout(wait_until - now).saturating_sub(1), + winuser::QS_ALLEVENTS, + winuser::MWMO_INPUTAVAILABLE, + ); + if resume_reason == winerror::WAIT_TIMEOUT { + winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + wait_until_opt = None; + } + } else { + winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + wait_until_opt = None; } } } @@ -461,6 +468,8 @@ impl EventLoopProxy { } } +type WaitUntilInstantBox = Box; + lazy_static! { // Message sent by the `EventLoopProxy` when we want to wake up the thread. // WPARAM and LPARAM are unused. @@ -477,6 +486,29 @@ lazy_static! { winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8) } }; + static ref PROCESS_NEW_EVENTS_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr() as *const i8) + } + }; + /// lparam is the wait thread's message id. + static ref SEND_WAIT_THREAD_ID_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr() as *const i8) + } + }; + /// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should + /// be sent. + static ref WAIT_UNTIL_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr() as *const i8) + } + }; + static ref CANCEL_WAIT_UNTIL_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr() as *const i8) + } + }; // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub static ref DESTROY_MSG_ID: u32 = { @@ -519,7 +551,7 @@ lazy_static! { }; } -fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> (HWND, Sender) { +fn create_event_target_window() -> HWND { unsafe { let window = winuser::CreateWindowExW( winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, @@ -543,7 +575,15 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> // the LAYERED style. (winuser::WS_VISIBLE | winuser::WS_POPUP) as _, ); + window + } +} +fn subclass_event_target_window( + window: HWND, + event_loop_runner: EventLoopRunnerShared, +) -> Sender { + unsafe { let (tx, rx) = mpsc::channel(); let subclass_input = ThreadMsgTargetSubclassInput { @@ -559,7 +599,7 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> ); assert_eq!(subclass_result, 1); - (window, tx) + tx } } @@ -582,6 +622,7 @@ unsafe fn release_mouse(window_state: &mut WindowState) { const WINDOW_SUBCLASS_ID: UINT_PTR = 0; const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1; pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { + subclass_input.event_loop_runner.register_window(window); let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = unsafe { commctrl::SetWindowSubclass( @@ -601,6 +642,68 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } +/// Flush redraw events for Winit's windows. +/// +/// Winit's API guarantees that all redraw events will be clustered together and dispatched all at +/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple +/// windows have had redraws scheduled, but an input event is pushed to the message queue between +/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows +/// will dispatch the input event immediately instead of flushing all the redraw events. This +/// function explicitly pulls all of Winit's redraw events out of the event queue so that they +/// always all get processed in one fell swoop. +/// +/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, +/// it won't flush the redraw events and will return `false`. +#[must_use] +unsafe fn flush_paint_messages( + except: Option, + runner: &EventLoopRunner, +) -> bool { + if !runner.redrawing() { + runner.main_events_cleared(); + let mut msg = mem::zeroed(); + runner.owned_windows(|redraw_window| { + if Some(redraw_window) == except { + return; + } + + if 0 == winuser::PeekMessageW( + &mut msg, + redraw_window, + winuser::WM_PAINT, + winuser::WM_PAINT, + winuser::PM_REMOVE | winuser::PM_QS_PAINT, + ) { + return; + } + + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + }); + true + } else { + false + } +} + +unsafe fn process_control_flow(runner: &EventLoopRunner) { + match runner.control_flow() { + ControlFlow::Poll => { + winuser::PostMessageW(runner.thread_msg_target(), *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + } + ControlFlow::Wait => (), + ControlFlow::WaitUntil(until) => { + winuser::PostThreadMessageW( + runner.wait_thread_id(), + *WAIT_UNTIL_MSG_ID, + 0, + Box::into_raw(WaitUntilInstantBox::new(until)) as LPARAM, + ); + } + ControlFlow::Exit => (), + } +} + /// Emit a `ModifiersChanged` event whenever modifiers have changed. fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { use crate::event::WindowEvent::ModifiersChanged; @@ -639,20 +742,37 @@ unsafe extern "system" fn public_window_callback( ) -> LRESULT { let subclass_input = &*(subclass_input_ptr as *const SubclassInput); - match msg { + winuser::RedrawWindow( + subclass_input.event_loop_runner.thread_msg_target(), + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { winuser::WM_ENTERSIZEMOVE => { - subclass_input.event_loop_runner.set_modal_loop(true); + subclass_input + .window_state + .lock() + .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); 0 } + winuser::WM_EXITSIZEMOVE => { - subclass_input.event_loop_runner.set_modal_loop(false); + subclass_input + .window_state + .lock() + .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); 0 } + winuser::WM_NCCREATE => { enable_non_client_dpi_scaling(window); commctrl::DefSubclassProc(window, msg, wparam, lparam) } - winuser::WM_NCLBUTTONDOWN => { if wparam == winuser::HTCAPTION as _ { winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0); @@ -676,6 +796,7 @@ unsafe extern "system" fn public_window_callback( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); + subclass_input.event_loop_runner.remove_window(window); drop(subclass_input); Box::from_raw(subclass_input_ptr as *mut SubclassInput); @@ -683,7 +804,25 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_PAINT => { - subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); + if subclass_input.event_loop_runner.should_buffer() { + // this branch can happen in response to `UpdateWindow`, if win32 decides to + // redraw the window outside the normal flow of the event loop. + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } else { + let managing_redraw = + flush_paint_messages(Some(window), &subclass_input.event_loop_runner); + subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); + if managing_redraw { + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); + } + } + commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1583,11 +1722,19 @@ unsafe extern "system" fn public_window_callback( }, }); - // Unset maximized if we're changing the window's size. - if new_physical_inner_size != old_physical_inner_size { - WindowState::set_window_flags(subclass_input.window_state.lock(), window, |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); + let dragging_window: bool; + + { + let window_state = subclass_input.window_state.lock(); + dragging_window = window_state + .window_flags() + .contains(WindowFlags::MARKER_IN_SIZE_MOVE); + // Unset maximized if we're changing the window's size. + if new_physical_inner_size != old_physical_inner_size { + WindowState::set_window_flags(window_state, window, |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + } } let new_outer_rect: RECT; @@ -1612,9 +1759,8 @@ unsafe extern "system" fn public_window_callback( ) .unwrap_or(conservative_rect); - // If we're not dragging the window, offset the window so that the cursor's + // If we're dragging the window, offset the window so that the cursor's // relative horizontal position in the title bar is preserved. - let dragging_window = subclass_input.event_loop_runner.in_modal_loop(); if dragging_window { let bias = { let cursor_pos = { @@ -1742,7 +1888,12 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) } } - } + }; + + subclass_input + .event_loop_runner + .catch_unwind(callback) + .unwrap_or(-1) } unsafe extern "system" fn thread_event_target_callback( @@ -1754,7 +1905,21 @@ unsafe extern "system" fn thread_event_target_callback( subclass_input_ptr: DWORD_PTR, ) -> LRESULT { let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); - match msg { + let runner = subclass_input.event_loop_runner.clone(); + + if msg != winuser::WM_PAINT { + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { winuser::WM_DESTROY => { Box::from_raw(subclass_input); drop(subclass_input); @@ -1764,52 +1929,20 @@ unsafe extern "system" fn thread_event_target_callback( // when the event queue has been emptied. See `process_event` for more details. winuser::WM_PAINT => { winuser::ValidateRect(window, ptr::null()); - let queue_call_again = || { - winuser::RedrawWindow( - window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - }; - let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop(); - if in_modal_loop { - let runner = &subclass_input.event_loop_runner; - runner.main_events_cleared(); - // Drain eventual WM_PAINT messages sent if user called request_redraw() - // during handling of MainEventsCleared. - let mut msg = mem::zeroed(); - loop { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - winuser::PM_QS_PAINT | winuser::PM_REMOVE, - ) { - break; - } - - if msg.hwnd != window { - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } - } - runner.redraw_events_cleared(); - match runner.control_flow() { - // Waiting is handled by the modal loop. - ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - runner.new_events(); - queue_call_again(); - } - ControlFlow::Poll => { - runner.new_events(); - queue_call_again(); - } - } + // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw + // events, `handling_events` will return false and we won't emit a second + // `RedrawEventsCleared` event. + if subclass_input.event_loop_runner.handling_events() { + // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` + // doesn't call WM_PAINT for the thread event target (i.e. this window). + assert!(flush_paint_messages( + None, + &subclass_input.event_loop_runner + )); + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); } + 0 } @@ -1940,6 +2073,49 @@ unsafe extern "system" fn thread_event_target_callback( function(); 0 } + _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { + winuser::PostThreadMessageW( + subclass_input.event_loop_runner.wait_thread_id(), + *CANCEL_WAIT_UNTIL_MSG_ID, + 0, + 0, + ); + + // if the control_flow is WaitUntil, make sure the given moment has actually passed + // before emitting NewEvents + if let ControlFlow::WaitUntil(wait_until) = + subclass_input.event_loop_runner.control_flow() + { + let mut msg = mem::zeroed(); + while Instant::now() < wait_until { + if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { + // This works around a "feature" in PeekMessageW. If the message PeekMessageW + // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't + // have an update region), PeekMessageW will remove that window from the + // redraw queue even though we told it not to remove messages from the + // queue. We fix it by re-dispatching an internal paint message to that + // window. + if msg.message == winuser::WM_PAINT { + let mut rect = mem::zeroed(); + if 0 == winuser::GetUpdateRect(msg.hwnd, &mut rect, 0) { + winuser::RedrawWindow( + msg.hwnd, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + } + + break; + } + } + } + subclass_input.event_loop_runner.poll(); + 0 + } _ => commctrl::DefSubclassProc(window, msg, wparam, lparam), - } + }; + + runner.catch_unwind(callback).unwrap_or(-1) } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index e5c062b035..258a40c08c 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -1,475 +1,410 @@ -use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::{HashSet, VecDeque}, + mem, panic, ptr, + rc::Rc, + time::Instant, +}; -use winapi::{shared::windef::HWND, um::winuser}; +use winapi::{ + shared::{minwindef::DWORD, windef::HWND}, + um::winuser, +}; use crate::{ dpi::PhysicalSize, event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::event_loop::{util, EventLoop}, + platform_impl::platform::util, window::WindowId, }; -pub(crate) type EventLoopRunnerShared = Rc>; -pub(crate) struct ELRShared { - runner: RefCell>>, - buffer: RefCell>>, -} +pub(crate) type EventLoopRunnerShared = Rc>; +pub(crate) struct EventLoopRunner { + // The event loop's win32 handles + thread_msg_target: HWND, + wait_thread_id: DWORD, + + control_flow: Cell, + runner_state: Cell, + last_events_cleared: Cell, + + event_handler: Cell, &mut ControlFlow)>>>, + event_buffer: RefCell>>, + + owned_windows: Cell>, -struct EventLoopRunner { - control_flow: ControlFlow, - runner_state: RunnerState, - modal_redraw_window: HWND, - in_modal_loop: bool, - event_handler: Box, &mut ControlFlow)>, - panic_error: Option, + panic_error: Cell>, } pub type PanicError = Box; -pub enum BufferedEvent { +/// See `move_state_to` function for details on how the state loop works. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum RunnerState { + /// The event loop has just been created, and an `Init` event must be sent. + Uninitialized, + /// The event loop is idling. + Idle, + /// The event loop is handling the OS's events and sending them to the user's callback. + /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. + HandlingMainEvents, + /// The event loop is handling the redraw events and sending them to the user's callback. + /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. + HandlingRedrawEvents, +} + +enum BufferedEvent { Event(Event<'static, T>), ScaleFactorChanged(WindowId, f64, PhysicalSize), } -impl BufferedEvent { - pub fn from_event(event: Event<'_, T>) -> BufferedEvent { - match event { - Event::WindowEvent { - event: - WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - }, - window_id, - } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), - event => BufferedEvent::Event(event.to_static().unwrap()), +impl EventLoopRunner { + pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: DWORD) -> EventLoopRunner { + EventLoopRunner { + thread_msg_target, + wait_thread_id, + runner_state: Cell::new(RunnerState::Uninitialized), + control_flow: Cell::new(ControlFlow::Poll), + panic_error: Cell::new(None), + last_events_cleared: Cell::new(Instant::now()), + event_handler: Cell::new(None), + event_buffer: RefCell::new(VecDeque::new()), + owned_windows: Cell::new(HashSet::new()), } } - pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { - match self { - Self::Event(event) => dispatch(event), - Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { - dispatch(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - util::set_inner_size_physical( - (window_id.0).0, - new_inner_size.width as _, - new_inner_size.height as _, - ); - } - } + pub(crate) unsafe fn set_event_handler(&self, f: F) + where + F: FnMut(Event<'_, T>, &mut ControlFlow), + { + let old_event_handler = self.event_handler.replace(mem::transmute::< + Option, &mut ControlFlow)>>, + Option, &mut ControlFlow)>>, + >(Some(Box::new(f)))); + assert!(old_event_handler.is_none()); + } + + pub(crate) fn reset_runner(&self) { + let EventLoopRunner { + thread_msg_target: _, + wait_thread_id: _, + runner_state, + panic_error, + control_flow, + last_events_cleared: _, + event_handler, + event_buffer: _, + owned_windows: _, + } = self; + runner_state.set(RunnerState::Uninitialized); + panic_error.set(None); + control_flow.set(ControlFlow::Poll); + event_handler.set(None); } } -impl ELRShared { - pub(crate) fn new() -> ELRShared { - ELRShared { - runner: RefCell::new(None), - buffer: RefCell::new(VecDeque::new()), - } +/// State retrieval functions. +impl EventLoopRunner { + pub fn thread_msg_target(&self) -> HWND { + self.thread_msg_target } - pub(crate) unsafe fn set_runner(&self, event_loop: &EventLoop, f: F) - where - F: FnMut(Event<'_, T>, &mut ControlFlow), - { - let mut runner = EventLoopRunner::new(event_loop, f); - { - let mut runner_ref = self.runner.borrow_mut(); - // Dispatch any events that were buffered during the creation of the window - self.dispatch_buffered_events(&mut runner); - *runner_ref = Some(runner); - } + pub fn wait_thread_id(&self) -> DWORD { + self.wait_thread_id } - pub(crate) fn destroy_runner(&self) { - *self.runner.borrow_mut() = None; + pub fn redrawing(&self) -> bool { + self.runner_state.get() == RunnerState::HandlingRedrawEvents } - pub(crate) fn new_events(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.new_events(); - // Dispatch any events that were buffered during the call `new_events` - self.dispatch_buffered_events(runner); + pub fn take_panic_error(&self) -> Result<(), PanicError> { + match self.panic_error.take() { + Some(err) => Err(err), + None => Ok(()), } } - pub(crate) fn send_event(&self, event: Event<'_, T>) { - if let Err(event) = self.send_event_unbuffered(event) { - // If the runner is already borrowed, we're in the middle of an event loop invocation. - // Add the event to a buffer to be processed later. - if let Event::RedrawRequested(_) = event { - panic!("buffering RedrawRequested event"); - } - self.buffer - .borrow_mut() - .push_back(BufferedEvent::from_event(event)); - } + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() } - fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.process_event(event); - // Dispatch any events that were buffered during the call to `process_event`. - self.dispatch_buffered_events(runner); - return Ok(()); - } - } - Err(event) + pub fn handling_events(&self) -> bool { + self.runner_state.get() != RunnerState::Idle } - fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner) { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - loop { - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(e) => e.dispatch_event(|e| runner.process_event(e)), - None => break, - } - } + pub fn should_buffer(&self) -> bool { + let handler = self.event_handler.take(); + let should_buffer = handler.is_none(); + self.event_handler.set(handler); + should_buffer } +} - pub(crate) fn main_events_cleared(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.main_events_cleared(); - if !self.buffer.borrow().is_empty() { - warn!("Buffered events while dispatching MainEventsCleared"); +/// Misc. functions +impl EventLoopRunner { + pub fn catch_unwind(&self, f: impl FnOnce() -> R) -> Option { + let panic_error = self.panic_error.take(); + if panic_error.is_none() { + let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); + + // Check to see if the panic error was set in a re-entrant call to catch_unwind inside + // of `f`. If it was, that error takes priority. If it wasn't, check if our call to + // catch_unwind caught any panics and set panic_error appropriately. + match self.panic_error.take() { + None => match result { + Ok(r) => Some(r), + Err(e) => { + self.panic_error.set(Some(e)); + None + } + }, + Some(e) => { + self.panic_error.set(Some(e)); + None + } } + } else { + self.panic_error.set(panic_error); + None } } + pub fn register_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.insert(window); + self.owned_windows.set(owned_windows); + } - pub(crate) fn redraw_events_cleared(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.redraw_events_cleared(); - if !self.buffer.borrow().is_empty() { - warn!("Buffered events while dispatching RedrawEventsCleared"); - } - } + pub fn remove_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.remove(&window); + self.owned_windows.set(owned_windows); } - pub(crate) fn destroy_loop(&self) { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.call_event_handler(Event::LoopDestroyed); - } + pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { + let mut owned_windows = self.owned_windows.take(); + for hwnd in &owned_windows { + f(*hwnd); } + let new_owned_windows = self.owned_windows.take(); + owned_windows.extend(&new_owned_windows); + self.owned_windows.set(owned_windows); } +} - pub(crate) fn take_panic_error(&self) -> Result<(), PanicError> { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.take_panic_error() - } else { - Ok(()) - } +/// Event dispatch functions. +impl EventLoopRunner { + pub(crate) unsafe fn poll(&self) { + self.move_state_to(RunnerState::HandlingMainEvents); } - pub(crate) fn set_modal_loop(&self, in_modal_loop: bool) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.in_modal_loop = in_modal_loop; - if in_modal_loop { - // jumpstart the modal loop - unsafe { - winuser::RedrawWindow( - runner.modal_redraw_window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - } + pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + if let Event::RedrawRequested(_) = event { + if self.runner_state.get() != RunnerState::HandlingRedrawEvents { + warn!("RedrawRequested dispatched without explicit MainEventsCleared"); + self.move_state_to(RunnerState::HandlingRedrawEvents); + } + self.call_event_handler(event); + } else { + if self.should_buffer() { + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self.event_buffer + .borrow_mut() + .push_back(BufferedEvent::from_event(event)) + } else { + self.move_state_to(RunnerState::HandlingMainEvents); + self.call_event_handler(event); + self.dispatch_buffered_events(); } } } - pub(crate) fn in_modal_loop(&self) -> bool { - let runner = self.runner.borrow(); - if let Some(ref runner) = *runner { - runner.in_modal_loop - } else { - false - } + pub(crate) unsafe fn main_events_cleared(&self) { + self.move_state_to(RunnerState::HandlingRedrawEvents); } - pub fn control_flow(&self) -> ControlFlow { - let runner_ref = self.runner.borrow(); - if let Some(ref runner) = *runner_ref { - runner.control_flow - } else { - ControlFlow::Exit - } + pub(crate) unsafe fn redraw_events_cleared(&self) { + self.move_state_to(RunnerState::Idle); } -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum RunnerState { - /// The event loop has just been created, and an `Init` event must be sent. - New, - /// The event loop is idling, and began idling at the given instant. - Idle(Instant), - /// The event loop has received a signal from the OS that the loop may resume, but no winit - /// events have been generated yet. We're waiting for an event to be processed or the events - /// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`. - DeferredNewEvents(Instant), - /// The event loop is handling the OS's events and sending them to the user's callback. - /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. - HandlingEvents, - /// The event loop is handling the redraw events and sending them to the user's callback. - /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. - HandlingRedraw, -} + pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) { + self.catch_unwind(|| { + let mut control_flow = self.control_flow.take(); + let mut event_handler = self.event_handler.take() + .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); -impl EventLoopRunner { - unsafe fn new(event_loop: &EventLoop, f: F) -> EventLoopRunner - where - F: FnMut(Event<'_, T>, &mut ControlFlow), - { - EventLoopRunner { - control_flow: ControlFlow::default(), - runner_state: RunnerState::New, - in_modal_loop: false, - modal_redraw_window: event_loop.window_target.p.thread_msg_target, - event_handler: mem::transmute::< - Box, &mut ControlFlow)>, - Box, &mut ControlFlow)>, - >(Box::new(f)), - panic_error: None, - } + if control_flow != ControlFlow::Exit { + event_handler(event, &mut control_flow); + } else { + event_handler(event, &mut ControlFlow::Exit); + } + + assert!(self.event_handler.replace(Some(event_handler)).is_none()); + self.control_flow.set(control_flow); + }); } - fn take_panic_error(&mut self) -> Result<(), PanicError> { - match self.panic_error.take() { - Some(err) => Err(err), - None => Ok(()), + unsafe fn dispatch_buffered_events(&self) { + loop { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + let buffered_event_opt = self.event_buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| self.call_event_handler(e)), + None => break, + } } } - fn new_events(&mut self) { - self.runner_state = match self.runner_state { - // If we're already handling events or have deferred `NewEvents`, we don't need to do - // do any processing. - RunnerState::HandlingEvents - | RunnerState::HandlingRedraw - | RunnerState::DeferredNewEvents(..) => self.runner_state, - - // Send the `Init` `NewEvents` and immediately move into event processing. - RunnerState::New => { - self.call_event_handler(Event::NewEvents(StartCause::Init)); - RunnerState::HandlingEvents + /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as + /// necessary to bring the internal `RunnerState` to the new runner state. + /// + /// The state transitions are defined as follows: + /// + /// ```text + /// Uninitialized + /// | + /// V + /// HandlingMainEvents + /// ^ | + /// | V + /// Idle <--- HandlingRedrawEvents + /// ``` + /// + /// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to + /// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state + /// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and + /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. + unsafe fn move_state_to(&self, new_runner_state: RunnerState) { + use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; + + match ( + self.runner_state.replace(new_runner_state), + new_runner_state, + ) { + (Uninitialized, Uninitialized) + | (Idle, Idle) + | (HandlingMainEvents, HandlingMainEvents) + | (HandlingRedrawEvents, HandlingRedrawEvents) => (), + + // State transitions that initialize the event loop. + (Uninitialized, HandlingMainEvents) => { + self.call_new_events(true); } - - // When `NewEvents` gets sent after an idle depends on the control flow... - // Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event. - // So we defer the `NewEvents` to when we actually process an event. - RunnerState::Idle(wait_start) => { - match self.control_flow { - // If we're polling, send `NewEvents` and immediately move into event processing. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - RunnerState::HandlingEvents - }, - // If the user was waiting until a specific time, the `NewEvents` call gets sent - // at varying times depending on the current time. - ControlFlow::WaitUntil(resume_time) => { - match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, we can tell the - // user that the resume time has been reached with `NewEvents` and immdiately move - // into event processing. - true => { - self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - })); - RunnerState::HandlingEvents - }, - // However, if the current time is EARLIER than the requested resume time, we - // don't want to send the `WaitCancelled` event until we know an event is being - // sent. Defer. - false => RunnerState::DeferredNewEvents(wait_start) - } - }, - // If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so - // we defer. - ControlFlow::Wait | - // `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane. - ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start), - } + (Uninitialized, HandlingRedrawEvents) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); } - }; - } - - fn process_event(&mut self, event: Event<'_, T>) { - // If we're in the modal loop, we need to have some mechanism for finding when the event - // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities - // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have - // been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when - // the events queue has been emptied. - if self.in_modal_loop { - unsafe { - winuser::RedrawWindow( - self.modal_redraw_window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); + (Uninitialized, Idle) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); } - } + (_, Uninitialized) => panic!("cannot move state to Uninitialized"), - // If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're - // already in processing nothing happens with this call. - self.new_events(); - - // Now that an event has been received, we have to send any `NewEvents` calls that were - // deferred. - if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state { - match self.control_flow { - ControlFlow::Exit | ControlFlow::Wait => { - self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled { - start: wait_start, - requested_resume: None, - })) - } - ControlFlow::WaitUntil(resume_time) => { - let start_cause = match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, the resume time - // has been reached. - true => StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - // Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled. - false => StartCause::WaitCancelled { - start: wait_start, - requested_resume: Some(resume_time), - }, - }; - self.call_event_handler(Event::NewEvents(start_cause)); - } - // This can be reached if the control flow is changed to poll during a `RedrawRequested` - // that was sent after `MainEventsCleared`. - ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), + // State transitions that start the event handling process. + (Idle, HandlingMainEvents) => { + self.call_new_events(false); + } + (Idle, HandlingRedrawEvents) => { + self.call_new_events(false); + self.call_event_handler(Event::MainEventsCleared); } - self.runner_state = RunnerState::HandlingEvents; - } - match (self.runner_state, &event) { - (RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => { + (HandlingMainEvents, HandlingRedrawEvents) => { self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - self.call_event_handler(Event::RedrawRequested(*window_id)); } - (RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => { - self.call_event_handler(Event::RedrawRequested(*window_id)); + (HandlingMainEvents, Idle) => { + warn!("RedrawEventsCleared emitted without explicit MainEventsCleared"); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); } - (RunnerState::HandlingRedraw, _) => { - warn!( - "non-redraw event in redraw phase: {:?}", - event.map_nonuser_event::<()>().ok() - ); + + (HandlingRedrawEvents, Idle) => { + self.call_redraw_events_cleared(); } - (_, _) => { - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(event); + (HandlingRedrawEvents, HandlingMainEvents) => { + warn!("NewEvents emitted without explicit RedrawEventsCleared"); + self.call_redraw_events_cleared(); + self.call_new_events(false); } } } - fn main_events_cleared(&mut self) { - match self.runner_state { - // If we were handling events, send the MainEventsCleared message. - RunnerState::HandlingEvents => { - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - } - - // We already cleared the main events, we don't have to do anything. - // This happens when process_events() processed a RedrawRequested event. - RunnerState::HandlingRedraw => {} - - // If we *weren't* handling events, we don't have to do anything. - RunnerState::New | RunnerState::Idle(..) => (), - - // Some control flows require a NewEvents call even if no events were received. This - // branch handles those. - RunnerState::DeferredNewEvents(wait_start) => { - match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; + unsafe fn call_new_events(&self, init: bool) { + let start_cause = match (init, self.control_flow()) { + (true, _) => StartCause::Init, + (false, ControlFlow::Poll) => StartCause::Poll, + (false, ControlFlow::Exit) | (false, ControlFlow::Wait) => StartCause::WaitCancelled { + requested_resume: None, + start: self.last_events_cleared.get(), + }, + (false, ControlFlow::WaitUntil(requested_resume)) => { + if Instant::now() < requested_resume { + StartCause::WaitCancelled { + requested_resume: Some(requested_resume), + start: self.last_events_cleared.get(), } - // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and MainEventsCleared event. - ControlFlow::WaitUntil(resume_time) => { - if Instant::now() >= resume_time { - self.call_event_handler(Event::NewEvents( - StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - )); - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - } + } else { + StartCause::ResumeTimeReached { + requested_resume, + start: self.last_events_cleared.get(), } - // If we deferred a wait and no events were received, the user doesn't have to - // get an event. - ControlFlow::Wait | ControlFlow::Exit => (), } } - } + }; + self.call_event_handler(Event::NewEvents(start_cause)); + self.dispatch_buffered_events(); + winuser::RedrawWindow( + self.thread_msg_target, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); } - fn redraw_events_cleared(&mut self) { - match self.runner_state { - // If we were handling redraws, send the RedrawEventsCleared message. - RunnerState::HandlingRedraw => { - self.call_event_handler(Event::RedrawEventsCleared); - self.runner_state = RunnerState::Idle(Instant::now()); - } - // No event was processed, we don't have to do anything. - RunnerState::DeferredNewEvents(_) => (), - // Should not happen. - _ => warn!( - "unexpected state in redraw_events_cleared: {:?}", - self.runner_state - ), + unsafe fn call_redraw_events_cleared(&self) { + self.call_event_handler(Event::RedrawEventsCleared); + self.last_events_cleared.set(Instant::now()); + } +} + +impl BufferedEvent { + pub fn from_event(event: Event<'_, T>) -> BufferedEvent { + match event { + Event::WindowEvent { + event: + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + window_id, + } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), + event => BufferedEvent::Event(event.to_static().unwrap()), } } - fn call_event_handler(&mut self, event: Event<'_, T>) { - if self.panic_error.is_none() { - let EventLoopRunner { - ref mut panic_error, - ref mut event_handler, - ref mut control_flow, - .. - } = self; - *panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| { - if *control_flow != ControlFlow::Exit { - (*event_handler)(event, control_flow); - } else { - (*event_handler)(event, &mut ControlFlow::Exit); - } - })) - .err(); + pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { + match self { + Self::Event(event) => dispatch(event), + Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { + dispatch(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + util::set_inner_size_physical( + (window_id.0).0, + new_inner_size.width as _, + new_inner_size.height as _, + ); + } } } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 11794821ad..124090813c 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -80,7 +80,9 @@ bitflags! { /// window's state to match our stored state. This controls whether to accept those changes. const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10; - const MINIMIZED = 1 << 11; + const MARKER_IN_SIZE_MOVE = 1 << 11; + + const MINIMIZED = 1 << 12; const FULLSCREEN_AND_MASK = !( WindowFlags::DECORATIONS.bits | From 007b195a5ec4228daf4512f723c050eac7314fa6 Mon Sep 17 00:00:00 2001 From: Francesca Lovebloom Date: Mon, 4 May 2020 15:55:58 -0700 Subject: [PATCH 19/56] iOS: convert touch positions to physical (#1551) --- CHANGELOG.md | 1 + src/platform_impl/ios/view.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9259fd55b..d9c79649b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. +- On iOS, touch positions are now properly converted to physical pixels. # 0.22.1 (2020-04-16) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 77f3fc5995..5481b8eff5 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -6,6 +6,7 @@ use objc::{ }; use crate::{ + dpi::PhysicalPosition, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ @@ -209,7 +210,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { if touch == nil { break; } - let location: CGPoint = msg_send![touch, locationInView: nil]; + let logical_location: CGPoint = msg_send![touch, locationInView: nil]; let touch_type: UITouchType = msg_send![touch, type]; let force = if os_supports_force { let trait_collection: id = msg_send![object, traitCollection]; @@ -248,12 +249,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { _ => panic!("unexpected touch phase: {:?}", phase as i32), }; + let physical_location = { + let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; + PhysicalPosition::from_logical::<(f64, f64), f64>( + (logical_location.x as _, logical_location.y as _), + scale_factor, + ) + }; touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Touch(Touch { device_id: RootDeviceId(DeviceId { uiscreen }), id: touch_id, - location: (location.x as f64, location.y as f64).into(), + location: physical_location, force, phase, }), From b8828105cf6231e249da279851285aecd2970b4b Mon Sep 17 00:00:00 2001 From: Jasper De Sutter Date: Wed, 6 May 2020 15:27:49 +0200 Subject: [PATCH 20/56] add android NDK event loop (#1556) * add android NDK event loop * add Android build documentation & cargo-apk to CI Co-authored-by: David Craven --- .github/workflows/ci.yml | 34 +- CHANGELOG.md | 1 + Cargo.toml | 6 +- README.md | 19 + src/platform/android.rs | 37 +- src/platform_impl/android/ffi.rs | 122 ------ src/platform_impl/android/mod.rs | 725 +++++++++++++++++-------------- 7 files changed, 465 insertions(+), 479 deletions(-) delete mode 100644 src/platform_impl/android/ffi.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 459844569c..b98dbc56b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,20 +37,21 @@ jobs: - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web # doesn't currently work on Linux. - - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web } - - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web } env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" FEATURES: ${{ format(',{0}', matrix.platform.features ) }} - WEB: ${{ matrix.platform.web }} + CMD: ${{ matrix.platform.cmd }} runs-on: ${{ matrix.platform.os }} steps: @@ -70,6 +71,9 @@ jobs: - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') run: sudo apt-get update && sudo apt-get install gcc-multilib + - name: Install cargo-apk + if: contains(matrix.platform.target, 'android') + run: cargo install cargo-apk - name: Install cargo-web continue-on-error: true if: contains(matrix.platform.target, 'wasm32') @@ -78,29 +82,35 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES - name: Build shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build tests shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Run tests shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build with serde enabled shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Build tests with serde enabled shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Run tests with serde enabled shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c79649b1..4ef3d12c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. +- On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. # 0.22.1 (2020-04-16) diff --git a/Cargo.toml b/Cargo.toml index 0b6f833f4e..cb466f9f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,10 @@ bitflags = "1" image = "0.23" simple_logger = "1" -[target.'cfg(target_os = "android")'.dependencies.android_glue] -version = "0.2" +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.1.0" +ndk-sys = "0.1.0" +ndk-glue = "0.1.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.3" diff --git a/README.md b/README.md index 3937622557..95aad64afd 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y the element of the `` element (in the example you would retrieve it via `document.getElementById("my_id")`). More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html). - Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created. + +#### Android + +This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. + +Running on an Android device needs a dynamic system library, add this to Cargo.toml: +```toml +[[example]] +name = "request_redraw_threaded" +crate-type = ["cdylib"] +``` + +And add this to the example file to add the native activity glue: +```rust +#[cfg(target_os = "android")] +ndk_glue::ndk_glue!(main); +``` + +And run the application with `cargo apk run --example request_redraw_threaded` diff --git a/src/platform/android.rs b/src/platform/android.rs index dafb7a391a..b4e9917642 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,32 +1,39 @@ #![cfg(any(target_os = "android"))] -use crate::{EventLoop, Window, WindowBuilder}; -use std::os::raw::c_void; +use crate::{ + event_loop::{EventLoop, EventLoopWindowTarget}, + window::{Window, WindowBuilder}, +}; +use ndk::configuration::Configuration; +use ndk_glue::Rect; /// Additional methods on `EventLoop` that are specific to Android. -pub trait EventLoopExtAndroid { - /// Makes it possible for glutin to register a callback when a suspend event happens on Android - fn set_suspend_callback(&self, cb: Option ()>>); -} +pub trait EventLoopExtAndroid {} -impl EventLoopExtAndroid for EventLoop { - fn set_suspend_callback(&self, cb: Option ()>>) { - self.event_loop.set_suspend_callback(cb); - } -} +impl EventLoopExtAndroid for EventLoop {} + +/// Additional methods on `EventLoopWindowTarget` that are specific to Android. +pub trait EventLoopWindowTargetExtAndroid {} /// Additional methods on `Window` that are specific to Android. pub trait WindowExtAndroid { - fn native_window(&self) -> *const c_void; + fn content_rect(&self) -> Rect; + + fn config(&self) -> Configuration; } impl WindowExtAndroid for Window { - #[inline] - fn native_window(&self) -> *const c_void { - self.window.native_window() + fn content_rect(&self) -> Rect { + self.window.content_rect() + } + + fn config(&self) -> Configuration { + self.window.config() } } +impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} + /// Additional methods on `WindowBuilder` that are specific to Android. pub trait WindowBuilderExtAndroid {} diff --git a/src/platform_impl/android/ffi.rs b/src/platform_impl/android/ffi.rs deleted file mode 100644 index 93a59b8247..0000000000 --- a/src/platform_impl/android/ffi.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![allow(dead_code)] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] - -use libc; -use std::os::raw; - -#[link(name = "android")] -#[link(name = "EGL")] -#[link(name = "GLESv2")] -extern "C" {} - -/** - ** asset_manager.h - **/ -pub type AAssetManager = raw::c_void; - -/** - ** native_window.h - **/ -pub type ANativeWindow = raw::c_void; - -extern "C" { - pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t; - pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t; -} - -/** - ** native_activity.h - **/ -pub type JavaVM = (); -pub type JNIEnv = (); -pub type jobject = *const libc::c_void; - -pub type AInputQueue = (); // FIXME: wrong -pub type ARect = (); // FIXME: wrong - -#[repr(C)] -pub struct ANativeActivity { - pub callbacks: *mut ANativeActivityCallbacks, - pub vm: *mut JavaVM, - pub env: *mut JNIEnv, - pub clazz: jobject, - pub internalDataPath: *const libc::c_char, - pub externalDataPath: *const libc::c_char, - pub sdkVersion: libc::int32_t, - pub instance: *mut libc::c_void, - pub assetManager: *mut AAssetManager, - pub obbPath: *const libc::c_char, -} - -#[repr(C)] -pub struct ANativeActivityCallbacks { - pub onStart: extern "C" fn(*mut ANativeActivity), - pub onResume: extern "C" fn(*mut ANativeActivity), - pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t), - pub onPause: extern "C" fn(*mut ANativeActivity), - pub onStop: extern "C" fn(*mut ANativeActivity), - pub onDestroy: extern "C" fn(*mut ANativeActivity), - pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int), - pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect), - pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity), - pub onLowMemory: extern "C" fn(*mut ANativeActivity), -} - -/** - ** looper.h - **/ -pub type ALooper = (); - -#[link(name = "android")] -extern "C" { - pub fn ALooper_forThread() -> *const ALooper; - pub fn ALooper_acquire(looper: *const ALooper); - pub fn ALooper_release(looper: *const ALooper); - pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper; - pub fn ALooper_pollOnce( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_pollAll( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_wake(looper: *const ALooper); - pub fn ALooper_addFd( - looper: *const ALooper, - fd: libc::c_int, - ident: libc::c_int, - events: libc::c_int, - callback: ALooper_callbackFunc, - data: *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int; -} - -pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0; - -pub const ALOOPER_POLL_WAKE: libc::c_int = -1; -pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2; -pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3; -pub const ALOOPER_POLL_ERROR: libc::c_int = -4; - -pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0; -pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1; -pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2; -pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3; -pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4; - -pub type ALooper_callbackFunc = - extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cf1893749c..ce26994b5d 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1,450 +1,519 @@ #![cfg(target_os = "android")] -extern crate android_glue; - -mod ffi; - +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, event, + event_loop::{self, ControlFlow}, + monitor, window, +}; +use ndk::{ + configuration::Configuration, + event::{InputEvent, MotionAction}, + looper::{ForeignLooper, Poll, ThreadLooper}, +}; +use ndk_glue::{Event, Rect}; use std::{ - cell::RefCell, collections::VecDeque, - fmt, - os::raw::c_void, - sync::mpsc::{channel, Receiver}, + sync::{Arc, Mutex, RwLock}, + time::{Duration, Instant}, }; -use crate::{ - error::{ExternalError, NotSupportedError}, - events::{Touch, TouchPhase}, - window::MonitorHandle as RootMonitorHandle, - CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, - WindowAttributes, WindowEvent, WindowId as RootWindowId, -}; -use raw_window_handle::{android::AndroidHandle, RawWindowHandle}; -use CreationError::OsError; - -pub(crate) use crate::icon::NoIcon as PlatformIcon; - -pub type OsError = std::io::Error; - -pub struct EventLoop { - event_rx: Receiver, - suspend_callback: RefCell ()>>>, +lazy_static! { + static ref CONFIG: RwLock = RwLock::new(Configuration::new()); } -#[derive(Clone)] -pub struct EventLoopProxy; +enum EventSource { + Callback, + InputQueue, + User, +} -impl EventLoop { - pub fn new() -> EventLoop { - let (tx, rx) = channel(); - android_glue::add_sender(tx); - EventLoop { - event_rx: rx, - suspend_callback: Default::default(), - } +fn poll(poll: Poll) -> Option { + match poll { + Poll::Event { data, .. } => match data as usize { + 0 => Some(EventSource::Callback), + 1 => Some(EventSource::InputQueue), + _ => unreachable!(), + }, + Poll::Timeout => None, + Poll::Wake => Some(EventSource::User), + Poll::Callback => unreachable!(), } +} - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb - } +pub struct EventLoop { + window_target: event_loop::EventLoopWindowTarget, + user_queue: Arc>>, +} - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle +impl EventLoop { + pub fn new() -> Self { + Self { + window_target: event_loop::EventLoopWindowTarget { + p: EventLoopWindowTarget { + _marker: std::marker::PhantomData, + }, + _marker: std::marker::PhantomData, + }, + user_queue: Default::default(), + } } - pub fn poll_events(&mut self, mut callback: F) + pub fn run(self, mut event_handler: F) -> ! where - F: FnMut(::Event), + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - while let Ok(event) = self.event_rx.try_recv() { - let e = match event { - android_glue::Event::EventMotion(motion) => { - let scale_factor = MonitorHandle.scale_factor(); - let location = LogicalPosition::from_physical( - (motion.x as f64, motion.y as f64), - scale_factor, - ); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Touch(Touch { - phase: match motion.action { - android_glue::MotionAction::Down => TouchPhase::Started, - android_glue::MotionAction::Move => TouchPhase::Moved, - android_glue::MotionAction::Up => TouchPhase::Ended, - android_glue::MotionAction::Cancel => TouchPhase::Cancelled, - }, - location, - force: None, // TODO - id: motion.pointer_id as u64, - device_id: DEVICE_ID, - }), - }) + let mut cf = ControlFlow::default(); + let mut first_event = None; + let mut start_cause = event::StartCause::Init; + let looper = ThreadLooper::for_thread().unwrap(); + let mut running = false; + + loop { + event_handler( + event::Event::NewEvents(start_cause), + self.window_target(), + &mut cf, + ); + + let mut redraw = false; + let mut resized = false; + + match first_event.take() { + Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { + Event::WindowCreated => { + event_handler(event::Event::Resumed, self.window_target(), &mut cf); + } + Event::WindowResized => resized = true, + Event::WindowRedrawNeeded => redraw = true, + Event::WindowDestroyed => { + event_handler(event::Event::Suspended, self.window_target(), &mut cf); + } + Event::Pause => running = false, + Event::Resume => running = true, + Event::ConfigChanged => { + let am = ndk_glue::native_activity().asset_manager(); + let config = Configuration::from_asset_manager(&am); + let old_scale_factor = MonitorHandle.scale_factor(); + *CONFIG.write().unwrap() = config; + let scale_factor = MonitorHandle.scale_factor(); + if (scale_factor - old_scale_factor).abs() < f64::EPSILON { + let mut size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::ScaleFactorChanged { + new_inner_size: &mut size, + scale_factor, + }, + }; + event_handler(event, self.window_target(), &mut cf); + } + } + _ => {} + }, + Some(EventSource::InputQueue) => { + if let Some(input_queue) = ndk_glue::input_queue().as_ref() { + while let Some(event) = input_queue.get_event() { + println!("event {:?}", event); + if let Some(event) = input_queue.pre_dispatch(event) { + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + match &event { + InputEvent::MotionEvent(motion_event) => { + let phase = match motion_event.action() { + MotionAction::Down => Some(event::TouchPhase::Started), + MotionAction::Up => Some(event::TouchPhase::Ended), + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => { + Some(event::TouchPhase::Cancelled) + } + _ => None, // TODO mouse events + }; + let pointer = motion_event.pointer_at_index(0); + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + + if let Some(phase) = phase { + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch(event::Touch { + device_id, + phase, + location, + id: 0, + force: None, + }), + }; + event_handler(event, self.window_target(), &mut cf); + } + } + InputEvent::KeyEvent(_) => {} // TODO + }; + input_queue.finish_event(event, true); + } + } + } } - android_glue::Event::InitWindow => { - // The activity went to foreground. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(false); + Some(EventSource::User) => { + let mut user_queue = self.user_queue.lock().unwrap(); + while let Some(event) = user_queue.pop_front() { + event_handler( + event::Event::UserEvent(event), + self.window_target(), + &mut cf, + ); } - Some(Event::Resumed) } - android_glue::Event::TermWindow => { - // The activity went to background. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(true); + None => {} + } + + event_handler( + event::Event::MainEventsCleared, + self.window_target(), + &mut cf, + ); + + if resized && running { + let size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Resized(size), + }; + event_handler(event, self.window_target(), &mut cf); + } + + if redraw && running { + let event = event::Event::RedrawRequested(window::WindowId(WindowId)); + event_handler(event, self.window_target(), &mut cf); + } + + event_handler( + event::Event::RedrawEventsCleared, + self.window_target(), + &mut cf, + ); + + match cf { + ControlFlow::Exit => panic!(), + ControlFlow::Poll => { + start_cause = event::StartCause::Poll; + } + ControlFlow::Wait => { + first_event = poll(looper.poll_all().unwrap()); + start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, } - Some(Event::Suspended) } - android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => { - // Activity Orientation changed or resized. - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - None + ControlFlow::WaitUntil(instant) => { + let start = Instant::now(); + let duration = if instant <= start { + Duration::default() } else { - let scale_factor = MonitorHandle.scale_factor(); - let physical_size = MonitorHandle.size(); - let size = LogicalSize::from_physical(physical_size, scale_factor); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Resized(size), - }) + instant - start + }; + first_event = poll(looper.poll_all_timeout(duration).unwrap()); + start_cause = if first_event.is_some() { + event::StartCause::WaitCancelled { + start, + requested_resume: Some(instant), + } + } else { + event::StartCause::ResumeTimeReached { + start, + requested_resume: instant, + } } } - android_glue::Event::WindowRedrawNeeded => { - // The activity needs to be redrawn. - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Redraw, - }) - } - android_glue::Event::Wake => Some(Event::Awakened), - _ => None, - }; - - if let Some(event) = e { - callback(event); } } } - pub fn set_suspend_callback(&self, cb: Option ()>>) { - *self.suspend_callback.borrow_mut() = cb; + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target } - pub fn run_forever(&mut self, mut callback: F) - where - F: FnMut(::Event) -> ::ControlFlow, - { - // Yeah that's a very bad implementation. - loop { - let mut control_flow = ::ControlFlow::Continue; - self.poll_events(|e| { - if let ::ControlFlow::Break = callback(e) { - control_flow = ::ControlFlow::Break; - } - }); - if let ::ControlFlow::Break = control_flow { - break; - } - ::std::thread::sleep(::std::time::Duration::from_millis(5)); - } + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle } - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(self.primary_monitor()); + v } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + queue: self.user_queue.clone(), + looper: ForeignLooper::for_thread().expect("called from event loop thread"), + } + } +} + +pub struct EventLoopProxy { + queue: Arc>>, + looper: ForeignLooper, } -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> { - android_glue::wake_event_loop(); +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + self.queue.lock().unwrap().push_back(event); + self.looper.wake(); Ok(()) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + queue: self.queue.clone(), + looper: self.looper.clone(), + } + } +} + +pub struct EventLoopWindowTarget { + _marker: std::marker::PhantomData, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId; impl WindowId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { WindowId } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; impl DeviceId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { DeviceId } } -pub struct Window { - native_window: *const c_void, -} - -#[derive(Clone)] -pub struct MonitorHandle; +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PlatformSpecificWindowBuilderAttributes; -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - dimensions: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } +pub struct Window; - let monitor_id_proxy = MonitorHandle { - name: self.name(), - dimensions: self.size(), - position: self.outer_position(), - scale_factor: self.scale_factor(), - }; +impl Window { + pub fn new( + _el: &EventLoopWindowTarget, + _window_attrs: window::WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + // FIXME this ignores requested window attributes + Ok(Self) + } - monitor_id_proxy.fmt(f) + pub fn id(&self) -> WindowId { + WindowId } -} -impl MonitorHandle { - #[inline] - pub fn name(&self) -> Option { - Some("Primary".to_string()) + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle } - #[inline] - pub fn size(&self) -> PhysicalSize { - unsafe { - let window = android_glue::native_window(); - ( - ffi::ANativeWindow_getWidth(window) as f64, - ffi::ANativeWindow_getHeight(window) as f64, - ) - .into() - } + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v } - #[inline] - pub fn outer_position(&self) -> PhysicalPosition { - // Android assumes single screen - (0, 0).into() + pub fn current_monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: MonitorHandle, + } } - #[inline] pub fn scale_factor(&self) -> f64 { - 1.0 + MonitorHandle.scale_factor() } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes; -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; - -impl Window { - pub fn new( - _: &EventLoop, - win_attribs: WindowAttributes, - _: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - return Err(OsError(format!("Android's native window is null"))); - } - android_glue::set_multitouch(true); - - Ok(Window { - native_window: native_window as *const _, - }) + pub fn request_redraw(&self) { + // TODO } - #[inline] - pub fn native_window(&self) -> *const c_void { - self.native_window + pub fn inner_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) } - #[inline] - pub fn set_title(&self, _: &str) { - // N/A + pub fn outer_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) } - #[inline] - pub fn show(&self) { - // N/A + pub fn set_outer_position(&self, _position: Position) { + // no effect } - #[inline] - pub fn hide(&self) { - // N/A + pub fn inner_size(&self) -> PhysicalSize { + self.outer_size() } - #[inline] - pub fn outer_position(&self) -> Option> { - // N/A - None + pub fn set_inner_size(&self, _size: Size) { + panic!("Cannot set window size on Android"); } - #[inline] - pub fn inner_position(&self) -> Option> { - // N/A - None + pub fn outer_size(&self) -> PhysicalSize { + MonitorHandle.size() } - #[inline] - pub fn set_outer_position(&self, _position: LogicalPosition) { - // N/A - } + pub fn set_min_inner_size(&self, _: Option) {} - #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option>) { - // N/A - } + pub fn set_max_inner_size(&self, _: Option) {} - #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option>) { - // N/A - } + pub fn set_title(&self, _title: &str) {} - #[inline] - pub fn set_resizable(&self, _resizable: bool) { - // N/A - } + pub fn set_visible(&self, _visibility: bool) {} - #[inline] - pub fn inner_size(&self) -> Option> { - if self.native_window.is_null() { - None - } else { - let scale_factor = self.scale_factor(); - let physical_size = self.current_monitor().size(); - Some(LogicalSize::from_physical(physical_size, scale_factor)) - } - } + pub fn set_resizable(&self, _resizeable: bool) {} - #[inline] - pub fn outer_size(&self) -> Option> { - self.inner_size() - } + pub fn set_minimized(&self, _minimized: bool) {} - #[inline] - pub fn set_inner_size(&self, _size: LogicalSize) { - // N/A - } + pub fn set_maximized(&self, _maximized: bool) {} - #[inline] - pub fn scale_factor(&self) -> f64 { - self.current_monitor().scale_factor() + pub fn set_fullscreen(&self, _monitor: Option) { + panic!("Cannot set fullscreen on Android"); } - #[inline] - pub fn set_cursor_icon(&self, _: CursorIcon) { - // N/A + pub fn fullscreen(&self) -> Option { + None } - #[inline] - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } + pub fn set_decorations(&self, _decorations: bool) {} - #[inline] - pub fn hide_cursor(&self, _hide: bool) { - // N/A - } + pub fn set_always_on_top(&self, _always_on_top: bool) {} - #[inline] - pub fn set_cursor_position( - &self, - _position: LogicalPosition, - ) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } + pub fn set_window_icon(&self, _window_icon: Option) {} - #[inline] - pub fn set_minimized(&self, _minimized: bool) { - unimplemented!() - } + pub fn set_ime_position(&self, _position: Position) {} + + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} - #[inline] - pub fn set_maximized(&self, _maximized: bool) { - // N/A - // Android has single screen maximized apps so nothing to do + pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) } - #[inline] - pub fn fullscreen(&self) -> Option { - // N/A - // Android has single screen maximized apps so nothing to do - None + pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) } - #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - // N/A - // Android has single screen maximized apps so nothing to do + pub fn set_cursor_visible(&self, _: bool) {} + + pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() { + unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } + } else { + panic!("native window null"); + }; + let mut handle = raw_window_handle::android::AndroidHandle::empty(); + handle.a_native_window = a_native_window; + raw_window_handle::RawWindowHandle::Android(handle) } - #[inline] - pub fn set_decorations(&self, _decorations: bool) { - // N/A + pub fn config(&self) -> Configuration { + CONFIG.read().unwrap().clone() } - #[inline] - pub fn set_always_on_top(&self, _always_on_top: bool) { - // N/A + pub fn content_rect(&self) -> Rect { + ndk_glue::content_rect() } +} - #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // N/A +#[derive(Default, Clone, Debug)] +pub struct OsError; + +use std::fmt::{self, Display, Formatter}; +impl Display for OsError { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "Android OS Error") } +} - #[inline] - pub fn set_ime_position(&self, _spot: LogicalPosition) { - // N/A +pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn name(&self) -> Option { + Some("Android Device".to_owned()) } - #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: MonitorHandle, + pub fn size(&self) -> PhysicalSize { + if let Some(native_window) = ndk_glue::native_window().as_ref() { + let width = native_window.width() as _; + let height = native_window.height() as _; + PhysicalSize::new(width, height) + } else { + PhysicalSize::new(0, 0) } } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() } - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle + pub fn scale_factor(&self) -> f64 { + let config = CONFIG.read().unwrap(); + config + .density() + .map(|dpi| dpi as f64 / 160.0) + .unwrap_or(1.0) + } + + pub fn video_modes(&self) -> impl Iterator { + let size = self.size().into(); + let mut v = Vec::new(); + // FIXME this is not the real refresh rate + // (it is guarunteed to support 32 bit color though) + v.push(monitor::VideoMode { + video_mode: VideoMode { + size, + bit_depth: 32, + refresh_rate: 60, + monitor: self.clone(), + }, + }); + v.into_iter() } +} - #[inline] - pub fn id(&self) -> WindowId { - WindowId +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct VideoMode { + size: (u32, u32), + bit_depth: u16, + refresh_rate: u16, + monitor: MonitorHandle, +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() } - #[inline] - pub fn raw_window_handle(&self) -> RawWindowHandle { - let handle = AndroidHandle { - a_native_window: self.native_window, - ..WindowsHandle::empty() - }; - RawWindowHandle::Android(handle) + pub fn bit_depth(&self) -> u16 { + self.bit_depth } -} -unsafe impl Send for Window {} -unsafe impl Sync for Window {} + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); + pub fn monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: self.monitor.clone(), + } + } +} From 8f230f4411582c3f67723bc944e5b3d617beb51a Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Wed, 6 May 2020 12:45:57 -0700 Subject: [PATCH 21/56] update core-video-sys version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a24df7eebf..7e8823a96a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] -git = "https://github.com/LuoZijun/rust-core-video-sys" +version = "0.1.4" default_features = false features = ["display_link"] From 3c38afdb4733572270805896160e8217a04051e9 Mon Sep 17 00:00:00 2001 From: j4qfrost Date: Thu, 7 May 2020 19:32:09 -0700 Subject: [PATCH 22/56] Update macOS dependencies (#1554) * update macos libs * modify dependency * changelog * update core-video-sys version --- CHANGELOG.md | 1 + Cargo.toml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef3d12c79..32f66702cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. +- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. diff --git a/Cargo.toml b/Cargo.toml index cb466f9f46..218fb2965a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,14 +42,14 @@ ndk-glue = "0.1.0" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.19.1" -core-foundation = "0.6" -core-graphics = "0.17.3" +cocoa = "0.20" +core-foundation = "0.7" +core-graphics = "0.19" dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] -version = "0.1.3" +version = "0.1.4" default_features = false features = ["display_link"] From 501341d9929f25b968b6a13893e70518079e6e8e Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Thu, 7 May 2020 23:18:17 -0700 Subject: [PATCH 23/56] move changelog line --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f66702cf..284b9961be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. +- On macOS, updated core-* dependencies and cocoa # 0.22.1 (2020-04-16) @@ -43,7 +44,6 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. -- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. From c7a33f926b2177a214a3d7515720a0111cf1c52c Mon Sep 17 00:00:00 2001 From: curldivergence Date: Fri, 15 May 2020 21:31:32 +0300 Subject: [PATCH 24/56] Fixed a couple of typos in repo description (#1568) --- FEATURES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 0918d358f5..cdd8cc9f13 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -109,8 +109,8 @@ If your PR makes notable changes to Winit's features, please update this section translating keypresses into UTF-8 characters, handling dead keys and IMEs. - **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled. - **Raw Device Events**: Capturing input from input devices without any OS filtering. -- **Gamepad/Joystick events**: Capturing input from gampads and joysticks. -- **Device movement events:**: Capturing input from the device gyroscope and accelerometer. +- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks. +- **Device movement events**: Capturing input from the device gyroscope and accelerometer. ## Platform ### Windows From bc19c04339e5b3b28cf7441c9802d52703cae6b5 Mon Sep 17 00:00:00 2001 From: j4qfrost Date: Fri, 15 May 2020 11:32:04 -0700 Subject: [PATCH 25/56] Fixed changelog line for core-* dependencies (#1561) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f66702cf..284b9961be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. +- On macOS, updated core-* dependencies and cocoa # 0.22.1 (2020-04-16) @@ -43,7 +44,6 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. -- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. From 878c179761cbc536ad24906486430ed2bb1d6092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Hornick=C3=BD?= Date: Fri, 15 May 2020 20:58:12 +0200 Subject: [PATCH 26/56] Implement Clone for 'static events (#1478) --- CHANGELOG.md | 1 + src/event.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 284b9961be..4086eefbcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. diff --git a/src/event.rs b/src/event.rs index 6b9fe1a630..47119fa807 100644 --- a/src/event.rs +++ b/src/event.rs @@ -118,6 +118,30 @@ pub enum Event<'a, T: 'static> { LoopDestroyed, } +impl Clone for Event<'static, T> { + fn clone(&self) -> Self { + use self::Event::*; + match self { + WindowEvent { window_id, event } => WindowEvent { + window_id: *window_id, + event: event.clone(), + }, + UserEvent(event) => UserEvent(event.clone()), + DeviceEvent { device_id, event } => DeviceEvent { + device_id: *device_id, + event: event.clone(), + }, + NewEvents(cause) => NewEvents(cause.clone()), + MainEventsCleared => MainEventsCleared, + RedrawRequested(wid) => RedrawRequested(*wid), + RedrawEventsCleared => RedrawEventsCleared, + LoopDestroyed => LoopDestroyed, + Suspended => Suspended, + Resumed => Resumed, + } + } +} + impl<'a, T> Event<'a, T> { pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { use self::Event::*; @@ -330,6 +354,97 @@ pub enum WindowEvent<'a> { ThemeChanged(Theme), } +impl Clone for WindowEvent<'static> { + fn clone(&self) -> Self { + use self::WindowEvent::*; + return match self { + Resized(size) => Resized(size.clone()), + Moved(pos) => Moved(pos.clone()), + CloseRequested => CloseRequested, + Destroyed => Destroyed, + DroppedFile(file) => DroppedFile(file.clone()), + HoveredFile(file) => HoveredFile(file.clone()), + HoveredFileCancelled => HoveredFileCancelled, + ReceivedCharacter(c) => ReceivedCharacter(*c), + Focused(f) => Focused(*f), + KeyboardInput { + device_id, + input, + is_synthetic, + } => KeyboardInput { + device_id: *device_id, + input: *input, + is_synthetic: *is_synthetic, + }, + + ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()), + #[allow(deprecated)] + CursorMoved { + device_id, + position, + modifiers, + } => CursorMoved { + device_id: *device_id, + position: *position, + modifiers: *modifiers, + }, + CursorEntered { device_id } => CursorEntered { + device_id: *device_id, + }, + CursorLeft { device_id } => CursorLeft { + device_id: *device_id, + }, + #[allow(deprecated)] + MouseWheel { + device_id, + delta, + phase, + modifiers, + } => MouseWheel { + device_id: *device_id, + delta: *delta, + phase: *phase, + modifiers: *modifiers, + }, + #[allow(deprecated)] + MouseInput { + device_id, + state, + button, + modifiers, + } => MouseInput { + device_id: *device_id, + state: *state, + button: *button, + modifiers: *modifiers, + }, + TouchpadPressure { + device_id, + pressure, + stage, + } => TouchpadPressure { + device_id: *device_id, + pressure: *pressure, + stage: *stage, + }, + AxisMotion { + device_id, + axis, + value, + } => AxisMotion { + device_id: *device_id, + axis: *axis, + value: *value, + }, + Touch(touch) => Touch(*touch), + ThemeChanged(theme) => ThemeChanged(theme.clone()), + ScaleFactorChanged { .. } => { + unreachable!("Static event can't be about scale factor changing") + } + }; + } +} + impl<'a> WindowEvent<'a> { pub fn to_static(self) -> Option> { use self::WindowEvent::*; @@ -809,8 +924,10 @@ pub enum VirtualKeyCode { Multiply, Mute, MyComputer, - NavigateForward, // also called "Prior" - NavigateBackward, // also called "Next" + // also called "Next" + NavigateForward, + // also called "Prior" + NavigateBackward, NextTrack, NoConvert, NumpadComma, From 49bcec1d274aaedc453a64bf6f53ea1ec59aa3a2 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 16 May 2020 12:27:16 -0400 Subject: [PATCH 27/56] Release 0.22.2 (#1570) --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4086eefbcb..3a0dbabf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.22.2 (2020-05-16) + - Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. diff --git a/Cargo.toml b/Cargo.toml index 218fb2965a..2f4903d9f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.22.1" +version = "0.22.2" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 95aad64afd..5e489631c6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.22.1" +winit = "0.22.2" ``` ## [Documentation](https://docs.rs/winit) From 6cfddfea21319ef7e28f4a6dc16e119ab033f550 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Thu, 21 May 2020 13:13:33 -0400 Subject: [PATCH 28/56] Prevent the default browser behavior of events (#1576) This stops things like page scrolling on spacebar / arrow keys --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/canvas.rs | 9 ++++++--- src/platform_impl/web/web_sys/canvas.rs | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0dbabf5e..250ef38a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index f6ed4866b9..64f6a18f10 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -10,9 +10,9 @@ use stdweb::js; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ - BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, KeyDownEvent, KeyPressEvent, - KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, PointerOutEvent, - PointerOverEvent, PointerUpEvent, + BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, KeyDownEvent, + KeyPressEvent, KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, + PointerOutEvent, PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ @@ -130,6 +130,7 @@ impl Canvas { F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -143,6 +144,7 @@ impl Canvas { F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -228,6 +230,7 @@ impl Canvas { F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| { + event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index a2755b95d2..e8fd3a2bba 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -135,6 +135,7 @@ impl Canvas { { self.on_keyboard_release = Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -149,6 +150,7 @@ impl Canvas { { self.on_keyboard_press = Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -242,6 +244,7 @@ impl Canvas { F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { + event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } From ff66bdda7ca5386cd159b4f44711fd97d7c9aef4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 22 May 2020 13:33:04 +0300 Subject: [PATCH 29/56] On Wayland, fix deadlock when calling set_inner_size from event loop Fixes #1571. --- CHANGELOG.md | 2 ++ src/platform_impl/linux/wayland/event_loop.rs | 3 +++ src/platform_impl/linux/wayland/window.rs | 14 ++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 250ef38a02..d15294b985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas +- On Wayland, fix deadlock when calling to `set_inner_size` from a callback. + # 0.22.2 (2020-05-16) - Added Clone implementation for 'static events. diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 5a3614c8b5..3262fe1e59 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -667,6 +667,8 @@ impl EventLoop { window_target.store.lock().unwrap().for_each_redraw_trigger( |refresh, frame_refresh, wid, frame| { if let Some(frame) = frame { + let mut frame = frame.lock().unwrap(); + if frame_refresh { frame.refresh(); if !refresh { @@ -751,6 +753,7 @@ impl EventLoop { if window.new_size.is_some() || window.new_scale_factor.is_some() { if let Some(frame) = window.frame { + let mut frame = frame.lock().unwrap(); // Update decorations state match window.decorations_action { Some(DecorationsAction::Hide) => frame.set_decorate(false), diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 12ae9ba1d5..5b680999a6 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -461,7 +461,7 @@ pub struct WindowStoreForEach<'a> { pub grab_cursor: Option, pub surface: &'a wl_surface::WlSurface, pub wid: WindowId, - pub frame: Option<&'a mut SWindow>, + pub frame: Option>>>, pub decorations_action: Option, } @@ -522,8 +522,7 @@ impl WindowStore { if let Some(scale_factor) = window.new_scale_factor { window.current_scale_factor = scale_factor; } - let opt_arc = window.frame.upgrade(); - let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + let frame = window.frame.upgrade(); let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; f(WindowStoreForEach { new_size: window.new_size.take(), @@ -534,7 +533,7 @@ impl WindowStore { grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), surface: &window.surface, wid: make_wid(&window.surface), - frame: opt_mutex_lock.as_mut().map(|m| &mut **m), + frame, decorations_action, }); // avoid re-spamming the event @@ -544,16 +543,15 @@ impl WindowStore { pub fn for_each_redraw_trigger(&mut self, mut f: F) where - F: FnMut(bool, bool, WindowId, Option<&mut SWindow>), + F: FnMut(bool, bool, WindowId, Option>>>), { for window in &mut self.windows { - let opt_arc = window.frame.upgrade(); - let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + let frame = window.frame.upgrade(); f( replace(&mut *window.need_refresh.lock().unwrap(), false), replace(&mut *window.need_frame_refresh.lock().unwrap(), false), make_wid(&window.surface), - opt_mutex_lock.as_mut().map(|m| &mut **m), + frame, ); } } From 03335cef851495e0a2843db7019516988740688b Mon Sep 17 00:00:00 2001 From: Andrew Slater Date: Sun, 24 May 2020 17:26:29 +0100 Subject: [PATCH 30/56] macOS: add function to hide other applications --- CHANGELOG.md | 1 + src/platform/macos.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d15294b985..279905d623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. +- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. # 0.22.2 (2020-05-16) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index b275c7538d..f07e2f6e77 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -215,6 +215,8 @@ impl MonitorHandleExtMacOS for MonitorHandle { pub trait EventLoopWindowTargetExtMacOS { /// Hide the entire application. In most applications this is typically triggered with Command-H. fn hide_application(&self); + /// Hide the other applications. In most applications this is typically triggered with Command+Option-H. + fn hide_other_applications(&self); } impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { @@ -223,4 +225,10 @@ impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; unsafe { msg_send![app, hide: 0] } } + + fn hide_other_applications(&self) { + let cls = objc::runtime::Class::get("NSApplication").unwrap(); + let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; + unsafe { msg_send![app, hideOtherApplications: 0] } + } } From a4121a2c2e13c274e27fc653df907ceaeca8132f Mon Sep 17 00:00:00 2001 From: Boqin Qin Date: Thu, 28 May 2020 00:24:08 +0800 Subject: [PATCH 31/56] platform_impl/linux/x11: fix deadlock in fn set_fullscreen_inner (#1579) Co-authored-by: Kirill Chibisov --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/window.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 279905d623..cc920994ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f178ba3aa5..f4cb3d4536 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -640,6 +640,7 @@ impl UnownedWindow { let flusher = self.set_fullscreen_hint(false); let mut shared_state_lock = self.shared_state.lock(); if let Some(position) = shared_state_lock.restore_position.take() { + drop(shared_state_lock); self.set_position_inner(position.0, position.1).queue(); } Some(flusher) From 5a6cfc314ea4890d79eefb01ef94bd46ca7858cf Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Tue, 9 Jun 2020 23:46:33 +0200 Subject: [PATCH 32/56] Macos fullscreen & dialog support with `run_return` (#1581) * Fix for fullscreen with run_return on mac * Cleanup * Removed a comment * fmt * This doesn't break exiting run_return anymore * Now you can also transition from code * Fmt & cleanup * Now using a atomic instead of a static bool * reinserted a line * Fmt * Added support for dialogs and child windows * Cargo fmt * Dialogs are now being shutdown properly * Cargo fmt * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/platform_impl/macos/app_state.rs | 61 +++++++++++++++------- src/platform_impl/macos/window.rs | 5 +- src/platform_impl/macos/window_delegate.rs | 13 ++++- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc920994ce..808a3c377d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. +- On MacOS, Fixed fullscreen and dialog support for `run_return`. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 135a3d2180..29fafbe5fa 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -92,6 +92,7 @@ impl EventHandler for EventLoopHandler { struct Handler { ready: AtomicBool, in_callback: AtomicBool, + dialog_is_closing: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, start_time: Mutex>, @@ -223,6 +224,8 @@ impl Handler { } } +pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false); + pub enum AppState {} impl AppState { @@ -336,29 +339,51 @@ impl AppState { } if HANDLER.should_exit() { unsafe { - let _: () = msg_send![NSApp(), stop: nil]; - - let pool = NSAutoreleasePool::new(nil); - - let windows: id = msg_send![NSApp(), windows]; + let app: id = NSApp(); + let windows: id = msg_send![app, windows]; let window: id = msg_send![windows, objectAtIndex:0]; + let window_count: usize = msg_send![windows, count]; assert_ne!(window, nil); - let dummy_event: id = msg_send![class!(NSEvent), - otherEventWithType: NSApplicationDefined - location: NSPoint::new(0.0, 0.0) - modifierFlags: 0 - timestamp: 0 - windowNumber: 0 - context: nil - subtype: 0 - data1: 0 - data2: 0 - ]; - // To stop event loop immediately, we need to post some event here. - let _: () = msg_send![window, postEvent: dummy_event atStart: YES]; + let dialog_open = if window_count > 1 { + let dialog: id = msg_send![windows, lastObject]; + let is_main_window: bool = msg_send![dialog, isMainWindow]; + msg_send![dialog, isVisible] && !is_main_window + } else { + false + }; + let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst); + let pool = NSAutoreleasePool::new(nil); + if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst) + && !dialog_open + && !dialog_is_closing + { + let _: () = msg_send![app, stop: nil]; + + let dummy_event: id = msg_send![class!(NSEvent), + otherEventWithType: NSApplicationDefined + location: NSPoint::new(0.0, 0.0) + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: 0 + data1: 0 + data2: 0 + ]; + // To stop event loop immediately, we need to post some event here. + let _: () = msg_send![window, postEvent: dummy_event atStart: YES]; + } pool.drain(); + + let window_has_focus = msg_send![window, isKeyWindow]; + if !dialog_open && window_has_focus && dialog_is_closing { + HANDLER.dialog_is_closing.store(false, Ordering::SeqCst); + } + if dialog_open { + HANDLER.dialog_is_closing.store(true, Ordering::SeqCst); + } }; } HANDLER.update_start_time(); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 2d692949ed..5505b661af 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -19,6 +19,7 @@ use crate::{ platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, + app_state::INTERRUPT_EVENT_LOOP_EXIT, ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, @@ -820,6 +821,8 @@ impl UnownedWindow { shared_state_lock.fullscreen = fullscreen.clone(); trace!("Unlocked shared state in `set_fullscreen`"); + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + match (&old_fullscreen, &fullscreen) { (&None, &Some(_)) => unsafe { util::toggle_full_screen_async( @@ -865,7 +868,7 @@ impl UnownedWindow { ) => unsafe { util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); }, - _ => (), + _ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst), } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index a89e99fcaf..8c49e773be 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,7 +1,7 @@ use std::{ f64, os::raw::c_void, - sync::{Arc, Weak}, + sync::{atomic::Ordering, Arc, Weak}, }; use cocoa::{ @@ -19,6 +19,7 @@ use crate::{ event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, + app_state::INTERRUPT_EVENT_LOOP_EXIT, event::{EventProxy, EventWrapper}, util::{self, IdRef}, view::ViewState, @@ -429,6 +430,9 @@ extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { /// Invoked when before enter fullscreen extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowWillEnterFullscreen:`"); + + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + with_state(this, |state| { state.with_window(|window| { trace!("Locked shared state in `window_will_enter_fullscreen`"); @@ -459,6 +463,9 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { /// Invoked when before exit fullscreen extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowWillExitFullScreen:`"); + + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + with_state(this, |state| { state.with_window(|window| { trace!("Locked shared state in `window_will_exit_fullscreen`"); @@ -492,6 +499,8 @@ extern "C" fn window_will_use_fullscreen_presentation_options( /// Invoked when entered fullscreen extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); + trace!("Triggered `windowDidEnterFullscreen:`"); with_state(this, |state| { state.initial_fullscreen = false; @@ -512,6 +521,8 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { /// Invoked when exited fullscreen extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); + trace!("Triggered `windowDidExitFullscreen:`"); with_state(this, |state| { state.with_window(|window| { From c1ea0dde92e1de9e029e7fbb2485a454aa81b918 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Mon, 15 Jun 2020 09:15:27 +0200 Subject: [PATCH 33/56] On Unix, add option to pick backends Add features 'x11' and 'wayland' to pick backends on Linux/BSD, with both enabled by default. Fixes #774. --- CHANGELOG.md | 1 + Cargo.toml | 9 +- README.md | 2 + build.rs | 21 + src/platform/unix.rs | 93 ++++- src/platform_impl/linux/mod.rs | 375 +++++++++--------- src/platform_impl/linux/wayland/event_loop.rs | 4 + src/platform_impl/linux/wayland/window.rs | 2 + src/platform_impl/linux/x11/mod.rs | 13 + src/platform_impl/linux/x11/window.rs | 1 + 10 files changed, 322 insertions(+), 199 deletions(-) create mode 100644 build.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 808a3c377d..c586be8d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas diff --git a/Cargo.toml b/Cargo.toml index 2f4903d9f2..017e5927ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,11 @@ default-target = "x86_64-unknown-linux-gnu" targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] [features] +default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] +x11 = ["x11-dl"] +wayland = ["wayland-client", "smithay-client-toolkit"] [dependencies] instant = "0.1" @@ -78,11 +81,11 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] } +wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] , optional = true } mio = "0.6" mio-extras = "2.0" -smithay-client-toolkit = "^0.6.6" -x11-dl = "2.18.5" +smithay-client-toolkit = { version = "^0.6.6", optional = true } +x11-dl = { version = "2.18.5", optional = true } percent-encoding = "2.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] diff --git a/README.md b/README.md index 5e489631c6..c7b312f6b8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Winit is only officially supported on the latest stable version of the Rust comp Winit provides the following features, which can be enabled in your `Cargo.toml` file: * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). +* `x11` (enabled by default): On Unix platform, compiles with the X11 backend +* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend ### Platform-specific usage diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..f1c4e9bf89 --- /dev/null +++ b/build.rs @@ -0,0 +1,21 @@ +#[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "x11"), + not(feature = "wayland") +))] +compile_error!("at least one of the \"x11\"/\"wayland\" features must be enabled"); + +#[cfg(all( + target_arch = "wasm32", + not(feature = "web-sys"), + not(feature = "stdweb") +))] +compile_error!("at least one of the \"web-sys\"/\"stdweb\" features must be enabled"); + +fn main() {} diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 6ab2d7dff4..13346a86cd 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -1,37 +1,46 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -use std::{os::raw, ptr, sync::Arc}; +use std::os::raw; +#[cfg(feature = "x11")] +use std::{ptr, sync::Arc}; +#[cfg(feature = "wayland")] use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme}; use crate::{ - dpi::Size, event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; +#[cfg(feature = "x11")] +use crate::dpi::Size; +#[cfg(feature = "x11")] +use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; use crate::platform_impl::{ - x11::{ffi::XVisualInfo, XConnection}, EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow, }; // TODO: stupid hack so that glutin can do its work #[doc(hidden)] +#[cfg(feature = "x11")] pub use crate::platform_impl::x11; - +#[cfg(feature = "x11")] pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; /// Additional methods on `EventLoopWindowTarget` that are specific to Unix. pub trait EventLoopWindowTargetExtUnix { /// True if the `EventLoopWindowTarget` uses Wayland. + #[cfg(feature = "wayland")] fn is_wayland(&self) -> bool; - /// + /// True if the `EventLoopWindowTarget` uses X11. + #[cfg(feature = "x11")] fn is_x11(&self) -> bool; #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; /// Returns a pointer to the `wl_display` object of wayland that is used by this @@ -40,35 +49,42 @@ pub trait EventLoopWindowTargetExtUnix { /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the winit `EventLoop` is destroyed. + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; } impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { #[inline] + #[cfg(feature = "wayland")] fn is_wayland(&self) -> bool { self.p.is_wayland() } #[inline] + #[cfg(feature = "x11")] fn is_x11(&self) -> bool { !self.p.is_wayland() } #[inline] #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option> { match self.p { LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.p { LinuxEventLoopWindowTarget::Wayland(ref p) => { Some(p.display().get_display_ptr() as *mut _) } + #[cfg(feature = "x11")] _ => None, } } @@ -82,6 +98,7 @@ pub trait EventLoopExtUnix { /// /// 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")] fn new_x11() -> Result where Self: Sized; @@ -92,6 +109,7 @@ pub trait EventLoopExtUnix { /// /// 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")] fn new_wayland() -> Self where Self: Sized; @@ -108,6 +126,7 @@ pub trait EventLoopExtUnix { /// /// 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; @@ -116,6 +135,7 @@ pub trait EventLoopExtUnix { /// /// 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; @@ -135,11 +155,13 @@ impl EventLoopExtUnix for EventLoop { } #[inline] + #[cfg(feature = "x11")] fn new_x11_any_thread() -> Result { LinuxEventLoop::new_x11_any_thread().map(wrap_ev) } #[inline] + #[cfg(feature = "wayland")] fn new_wayland_any_thread() -> Self { wrap_ev( LinuxEventLoop::new_wayland_any_thread() @@ -149,11 +171,13 @@ impl EventLoopExtUnix for EventLoop { } #[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() @@ -168,6 +192,7 @@ pub trait WindowExtUnix { /// Returns the ID of the `Window` xlib object that is used by this window. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + #[cfg(feature = "x11")] fn xlib_window(&self) -> Option; /// Returns a pointer to the `Display` object of xlib that is used by this window. @@ -175,14 +200,18 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "x11")] fn xlib_display(&self) -> Option<*mut raw::c_void>; + #[cfg(feature = "x11")] fn xlib_screen_id(&self) -> Option; #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. + #[cfg(feature = "x11")] fn set_urgent(&self, is_urgent: bool); /// This function returns the underlying `xcb_connection_t` of an xlib `Display`. @@ -190,6 +219,7 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_surface` object of wayland that is used by this window. @@ -197,6 +227,7 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "wayland")] fn wayland_surface(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_display` object of wayland that is used by this window. @@ -204,9 +235,11 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; /// Sets the color theme of the client side window decorations on wayland + #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T); /// Check if the window is ready for drawing @@ -221,73 +254,92 @@ pub trait WindowExtUnix { impl WindowExtUnix for Window { #[inline] + #[cfg(feature = "x11")] fn xlib_window(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_window()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn xlib_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_display()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn xlib_screen_id(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_screen_id()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_xconnection()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn set_urgent(&self, is_urgent: bool) { - if let LinuxWindow::X(ref w) = self.window { - w.set_urgent(is_urgent); + match self.window { + LinuxWindow::X(ref w) => w.set_urgent(is_urgent), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] + #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xcb_connection()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_surface(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _), + #[cfg(feature = "x11")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _), + #[cfg(feature = "x11")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T) { match self.window { LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)), + #[cfg(feature = "x11")] _ => {} } } @@ -300,20 +352,28 @@ impl WindowExtUnix for Window { /// Additional methods on `WindowBuilder` that are specific to Unix. pub trait WindowBuilderExtUnix { + #[cfg(feature = "x11")] fn with_x11_visual(self, visual_infos: *const T) -> Self; + #[cfg(feature = "x11")] fn with_x11_screen(self, screen_id: i32) -> Self; /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11. + #[cfg(feature = "x11")] fn with_class(self, class: String, instance: String) -> Self; /// Build window with override-redirect flag; defaults to false. Only relevant on X11. + #[cfg(feature = "x11")] fn with_override_redirect(self, override_redirect: bool) -> Self; /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11. + #[cfg(feature = "x11")] fn with_x11_window_type(self, x11_window_type: Vec) -> Self; /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. + #[cfg(feature = "x11")] fn with_gtk_theme_variant(self, variant: String) -> Self; /// Build window with resize increment hint. Only implemented on X11. + #[cfg(feature = "x11")] fn with_resize_increments>(self, increments: S) -> Self; /// Build window with base size hint. Only implemented on X11. + #[cfg(feature = "x11")] fn with_base_size>(self, base_size: S) -> Self; /// Build window with a given application ID. It should match the `.desktop` file distributed with @@ -321,60 +381,72 @@ pub trait WindowBuilderExtUnix { /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) + #[cfg(feature = "wayland")] fn with_app_id(self, app_id: String) -> Self; } impl WindowBuilderExtUnix for WindowBuilder { #[inline] + #[cfg(feature = "x11")] fn with_x11_visual(mut self, visual_infos: *const T) -> Self { - self.platform_specific.visual_infos = - Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); + { + self.platform_specific.visual_infos = + Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); + } self } #[inline] + #[cfg(feature = "x11")] fn with_x11_screen(mut self, screen_id: i32) -> Self { self.platform_specific.screen_id = Some(screen_id); self } #[inline] + #[cfg(feature = "x11")] fn with_class(mut self, instance: String, class: String) -> Self { self.platform_specific.class = Some((instance, class)); self } #[inline] + #[cfg(feature = "x11")] fn with_override_redirect(mut self, override_redirect: bool) -> Self { self.platform_specific.override_redirect = override_redirect; self } #[inline] + #[cfg(feature = "x11")] fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { self.platform_specific.x11_window_types = x11_window_types; self } #[inline] + #[cfg(feature = "x11")] fn with_gtk_theme_variant(mut self, variant: String) -> Self { self.platform_specific.gtk_theme_variant = Some(variant); self } #[inline] + #[cfg(feature = "x11")] fn with_resize_increments>(mut self, increments: S) -> Self { self.platform_specific.resize_increments = Some(increments.into()); self } #[inline] + #[cfg(feature = "x11")] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.base_size = Some(base_size.into()); self } #[inline] + #[cfg(feature = "wayland")] fn with_app_id(mut self, app_id: String) -> Self { self.platform_specific.app_id = Some(app_id); self @@ -395,6 +467,7 @@ impl MonitorHandleExtUnix for MonitorHandle { } /// Wrapper for implementing SCTK's theme trait. +#[cfg(feature = "wayland")] struct WaylandTheme(T); pub trait Theme: Send + 'static { @@ -432,6 +505,7 @@ pub trait Theme: Send + 'static { } } +#[cfg(feature = "wayland")] impl SCTKTheme for WaylandTheme { fn get_primary_color(&self, active: bool) -> [u8; 4] { self.0.primary_color(active) @@ -478,6 +552,7 @@ pub enum ButtonState { Disabled, } +#[cfg(feature = "wayland")] impl ButtonState { fn from_sctk(button_state: SCTKButtonState) -> Self { match button_state { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 4713038f38..613d0e231b 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -1,12 +1,27 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] - -use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc}; - +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +#[cfg(all(not(feature = "x11"), not(feature = "wayland")))] +compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); + +use std::{collections::VecDeque, env, fmt}; +#[cfg(feature = "x11")] +use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; + +#[cfg(feature = "x11")] use parking_lot::Mutex; use raw_window_handle::RawWindowHandle; +#[cfg(feature = "wayland")] use smithay_client_toolkit::reexports::client::ConnectError; +#[cfg(feature = "x11")] pub use self::x11::XNotSupported; +#[cfg(feature = "x11")] use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, @@ -20,7 +35,9 @@ use crate::{ pub(crate) use crate::icon::RgbaIcon as PlatformIcon; +#[cfg(feature = "wayland")] pub mod wayland; +#[cfg(feature = "x11")] pub mod x11; /// Environment variable specifying which backend should be used on unix platform. @@ -34,33 +51,52 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { + #[cfg(feature = "x11")] pub visual_infos: Option, + #[cfg(feature = "x11")] pub screen_id: Option, + #[cfg(feature = "x11")] pub resize_increments: Option, + #[cfg(feature = "x11")] pub base_size: Option, + #[cfg(feature = "x11")] pub class: Option<(String, String)>, + #[cfg(feature = "x11")] pub override_redirect: bool, + #[cfg(feature = "x11")] pub x11_window_types: Vec, + #[cfg(feature = "x11")] pub gtk_theme_variant: Option, + #[cfg(feature = "wayland")] pub app_id: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { + #[cfg(feature = "x11")] visual_infos: None, + #[cfg(feature = "x11")] screen_id: None, + #[cfg(feature = "x11")] resize_increments: None, + #[cfg(feature = "x11")] base_size: None, + #[cfg(feature = "x11")] class: None, + #[cfg(feature = "x11")] override_redirect: false, + #[cfg(feature = "x11")] x11_window_types: vec![XWindowType::Normal], + #[cfg(feature = "x11")] gtk_theme_variant: None, + #[cfg(feature = "wayland")] app_id: None, } } } +#[cfg(feature = "x11")] lazy_static! { pub static ref X11_BACKEND: Mutex, XNotSupported>> = Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); @@ -68,141 +104,159 @@ lazy_static! { #[derive(Debug, Clone)] pub enum OsError { + #[cfg(feature = "x11")] XError(XError), + #[cfg(feature = "x11")] XMisc(&'static str), } impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - OsError::XError(e) => f.pad(&e.description), - OsError::XMisc(e) => f.pad(e), + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + #[cfg(feature = "x11")] + OsError::XError(ref e) => _f.pad(&e.description), + #[cfg(feature = "x11")] + OsError::XMisc(ref e) => _f.pad(e), } } } pub enum Window { + #[cfg(feature = "x11")] X(x11::Window), + #[cfg(feature = "wayland")] Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum WindowId { + #[cfg(feature = "x11")] X(x11::WindowId), + #[cfg(feature = "wayland")] Wayland(wayland::WindowId), } impl WindowId { pub unsafe fn dummy() -> Self { - WindowId::Wayland(wayland::WindowId::dummy()) + #[cfg(feature = "wayland")] + return WindowId::Wayland(wayland::WindowId::dummy()); + #[cfg(all(not(feature = "wayland"), feature = "x11"))] + return WindowId::X(x11::WindowId::dummy()); } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { + #[cfg(feature = "x11")] X(x11::DeviceId), + #[cfg(feature = "wayland")] Wayland(wayland::DeviceId), } impl DeviceId { pub unsafe fn dummy() -> Self { - DeviceId::Wayland(wayland::DeviceId::dummy()) + #[cfg(feature = "wayland")] + return DeviceId::Wayland(wayland::DeviceId::dummy()); + #[cfg(all(not(feature = "wayland"), feature = "x11"))] + return DeviceId::X(x11::DeviceId::dummy()); } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { + #[cfg(feature = "x11")] X(x11::MonitorHandle), + #[cfg(feature = "wayland")] Wayland(wayland::MonitorHandle), } +/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` +/// expands to the equivalent of +/// ```ignore +/// match self { +/// Enum::X(foo) => foo.something(), +/// Enum::Wayland(foo) => foo.something(), +/// } +/// ``` +/// The result can be converted to another enum by adding `; as AnotherEnum` +macro_rules! x11_or_wayland { + (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => { + match $what { + #[cfg(feature = "x11")] + $enum::X($($c1)*) => $enum2::X($x), + #[cfg(feature = "wayland")] + $enum::Wayland($($c1)*) => $enum2::Wayland($x), + } + }; + (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => { + match $what { + #[cfg(feature = "x11")] + $enum::X($($c1)*) => $x, + #[cfg(feature = "wayland")] + $enum::Wayland($($c1)*) => $x, + } + }; +} + impl MonitorHandle { #[inline] pub fn name(&self) -> Option { - match self { - &MonitorHandle::X(ref m) => m.name(), - &MonitorHandle::Wayland(ref m) => m.name(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.name()) } #[inline] pub fn native_identifier(&self) -> u32 { - match self { - &MonitorHandle::X(ref m) => m.native_identifier(), - &MonitorHandle::Wayland(ref m) => m.native_identifier(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier()) } #[inline] pub fn size(&self) -> PhysicalSize { - match self { - &MonitorHandle::X(ref m) => m.size(), - &MonitorHandle::Wayland(ref m) => m.size(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.size()) } #[inline] pub fn position(&self) -> PhysicalPosition { - match self { - &MonitorHandle::X(ref m) => m.position(), - &MonitorHandle::Wayland(ref m) => m.position(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.position()) } #[inline] pub fn scale_factor(&self) -> f64 { - match self { - &MonitorHandle::X(ref m) => m.scale_factor(), - &MonitorHandle::Wayland(ref m) => m.scale_factor() as f64, - } + x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64) } #[inline] pub fn video_modes(&self) -> Box> { - match self { - MonitorHandle::X(m) => Box::new(m.video_modes()), - MonitorHandle::Wayland(m) => Box::new(m.video_modes()), - } + x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum VideoMode { + #[cfg(feature = "x11")] X(x11::VideoMode), + #[cfg(feature = "wayland")] Wayland(wayland::VideoMode), } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { - match self { - &VideoMode::X(ref m) => m.size(), - &VideoMode::Wayland(ref m) => m.size(), - } + x11_or_wayland!(match self; VideoMode(m) => m.size()) } #[inline] pub fn bit_depth(&self) -> u16 { - match self { - &VideoMode::X(ref m) => m.bit_depth(), - &VideoMode::Wayland(ref m) => m.bit_depth(), - } + x11_or_wayland!(match self; VideoMode(m) => m.bit_depth()) } #[inline] pub fn refresh_rate(&self) -> u16 { - match self { - &VideoMode::X(ref m) => m.refresh_rate(), - &VideoMode::Wayland(ref m) => m.refresh_rate(), - } + x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate()) } #[inline] pub fn monitor(&self) -> RootMonitorHandle { - match self { - &VideoMode::X(ref m) => m.monitor(), - &VideoMode::Wayland(ref m) => m.monitor(), - } + x11_or_wayland!(match self; VideoMode(m) => m.monitor()) } } @@ -214,9 +268,11 @@ impl Window { pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { match *window_target { + #[cfg(feature = "wayland")] EventLoopWindowTarget::Wayland(ref window_target) => { wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland) } + #[cfg(feature = "x11")] EventLoopWindowTarget::X(ref window_target) => { x11::Window::new(window_target, attribs, pl_attribs).map(Window::X) } @@ -225,232 +281,166 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - match self { - &Window::X(ref w) => WindowId::X(w.id()), - &Window::Wayland(ref w) => WindowId::Wayland(w.id()), - } + x11_or_wayland!(match self; Window(w) => w.id(); as WindowId) } #[inline] pub fn set_title(&self, title: &str) { - match self { - &Window::X(ref w) => w.set_title(title), - &Window::Wayland(ref w) => w.set_title(title), - } + x11_or_wayland!(match self; Window(w) => w.set_title(title)); } #[inline] pub fn set_visible(&self, visible: bool) { - match self { - &Window::X(ref w) => w.set_visible(visible), - &Window::Wayland(ref w) => w.set_visible(visible), - } + x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - match self { - &Window::X(ref w) => w.outer_position(), - &Window::Wayland(ref w) => w.outer_position(), - } + x11_or_wayland!(match self; Window(w) => w.outer_position()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { - match self { - &Window::X(ref m) => m.inner_position(), - &Window::Wayland(ref m) => m.inner_position(), - } + x11_or_wayland!(match self; Window(w) => w.inner_position()) } #[inline] pub fn set_outer_position(&self, position: Position) { - match self { - &Window::X(ref w) => w.set_outer_position(position), - &Window::Wayland(ref w) => w.set_outer_position(position), - } + x11_or_wayland!(match self; Window(w) => w.set_outer_position(position)) } #[inline] pub fn inner_size(&self) -> PhysicalSize { - match self { - &Window::X(ref w) => w.inner_size(), - &Window::Wayland(ref w) => w.inner_size(), - } + x11_or_wayland!(match self; Window(w) => w.inner_size()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { - match self { - &Window::X(ref w) => w.outer_size(), - &Window::Wayland(ref w) => w.outer_size(), - } + x11_or_wayland!(match self; Window(w) => w.outer_size()) } #[inline] pub fn set_inner_size(&self, size: Size) { - match self { - &Window::X(ref w) => w.set_inner_size(size), - &Window::Wayland(ref w) => w.set_inner_size(size), - } + x11_or_wayland!(match self; Window(w) => w.set_inner_size(size)) } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - match self { - &Window::X(ref w) => w.set_min_inner_size(dimensions), - &Window::Wayland(ref w) => w.set_min_inner_size(dimensions), - } + x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions)) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - match self { - &Window::X(ref w) => w.set_max_inner_size(dimensions), - &Window::Wayland(ref w) => w.set_max_inner_size(dimensions), - } + x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) } #[inline] pub fn set_resizable(&self, resizable: bool) { - match self { - &Window::X(ref w) => w.set_resizable(resizable), - &Window::Wayland(ref w) => w.set_resizable(resizable), - } + x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - match self { - &Window::X(ref w) => w.set_cursor_icon(cursor), - &Window::Wayland(ref w) => w.set_cursor_icon(cursor), - } + x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) } #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - match self { - &Window::X(ref window) => window.set_cursor_grab(grab), - &Window::Wayland(ref window) => window.set_cursor_grab(grab), - } + x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab)) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - match self { - &Window::X(ref window) => window.set_cursor_visible(visible), - &Window::Wayland(ref window) => window.set_cursor_visible(visible), - } + x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) } #[inline] pub fn scale_factor(&self) -> f64 { - match self { - &Window::X(ref w) => w.scale_factor(), - &Window::Wayland(ref w) => w.scale_factor() as f64, - } + x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - match self { - &Window::X(ref w) => w.set_cursor_position(position), - &Window::Wayland(ref w) => w.set_cursor_position(position), - } + x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position)) } #[inline] pub fn set_maximized(&self, maximized: bool) { - match self { - &Window::X(ref w) => w.set_maximized(maximized), - &Window::Wayland(ref w) => w.set_maximized(maximized), - } + x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) } #[inline] pub fn set_minimized(&self, minimized: bool) { - match self { - &Window::X(ref w) => w.set_minimized(minimized), - &Window::Wayland(ref w) => w.set_minimized(minimized), - } + x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) } #[inline] pub fn fullscreen(&self) -> Option { - match self { - &Window::X(ref w) => w.fullscreen(), - &Window::Wayland(ref w) => w.fullscreen(), - } + x11_or_wayland!(match self; Window(w) => w.fullscreen()) } #[inline] pub fn set_fullscreen(&self, monitor: Option) { - match self { - &Window::X(ref w) => w.set_fullscreen(monitor), - &Window::Wayland(ref w) => w.set_fullscreen(monitor), - } + x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor)) } #[inline] pub fn set_decorations(&self, decorations: bool) { - match self { - &Window::X(ref w) => w.set_decorations(decorations), - &Window::Wayland(ref w) => w.set_decorations(decorations), - } + x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations)) } #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { + pub fn set_always_on_top(&self, _always_on_top: bool) { match self { - &Window::X(ref w) => w.set_always_on_top(always_on_top), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_always_on_top(_always_on_top), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] - pub fn set_window_icon(&self, window_icon: Option) { + pub fn set_window_icon(&self, _window_icon: Option) { match self { - &Window::X(ref w) => w.set_window_icon(window_icon), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_window_icon(_window_icon), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] - pub fn set_ime_position(&self, position: Position) { + pub fn set_ime_position(&self, _position: Position) { match self { - &Window::X(ref w) => w.set_ime_position(position), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_ime_position(_position), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] pub fn request_redraw(&self) { - match self { - &Window::X(ref w) => w.request_redraw(), - &Window::Wayland(ref w) => w.request_redraw(), - } + x11_or_wayland!(match self; Window(w) => w.request_redraw()) } #[inline] pub fn current_monitor(&self) -> RootMonitorHandle { - match self { - &Window::X(ref window) => RootMonitorHandle { - inner: MonitorHandle::X(window.current_monitor()), - }, - &Window::Wayland(ref window) => RootMonitorHandle { - inner: MonitorHandle::Wayland(window.current_monitor()), - }, + RootMonitorHandle { + inner: x11_or_wayland!(match self; Window(window) => window.current_monitor(); as MonitorHandle), } } #[inline] pub fn available_monitors(&self) -> VecDeque { match self { + #[cfg(feature = "x11")] &Window::X(ref window) => window .available_monitors() .into_iter() .map(MonitorHandle::X) .collect(), + #[cfg(feature = "wayland")] &Window::Wayland(ref window) => window .available_monitors() .into_iter() @@ -461,20 +451,20 @@ impl Window { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { - match self { - &Window::X(ref window) => MonitorHandle::X(window.primary_monitor()), - &Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()), - } + x11_or_wayland!(match self; Window(window) => window.primary_monitor(); as MonitorHandle) } pub fn raw_window_handle(&self) -> RawWindowHandle { match self { + #[cfg(feature = "x11")] &Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()), + #[cfg(feature = "wayland")] &Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), } } } +#[cfg(feature = "x11")] unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, @@ -508,21 +498,22 @@ unsafe extern "C" fn x_error_callback( } pub enum EventLoop { + #[cfg(feature = "wayland")] Wayland(wayland::EventLoop), + #[cfg(feature = "x11")] X(x11::EventLoop), } pub enum EventLoopProxy { + #[cfg(feature = "x11")] X(x11::EventLoopProxy), + #[cfg(feature = "wayland")] Wayland(wayland::EventLoopProxy), } impl Clone for EventLoopProxy { fn clone(&self) -> Self { - match self { - EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()), - EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()), - } + x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) } } @@ -538,12 +529,18 @@ impl EventLoop { match env_var.as_str() { "x11" => { // TODO: propagate + #[cfg(feature = "x11")] return EventLoop::new_x11_any_thread() .expect("Failed to initialize X11 backend"); + #[cfg(not(feature = "x11"))] + panic!("x11 feature is not enabled") } "wayland" => { + #[cfg(feature = "wayland")] return EventLoop::new_wayland_any_thread() .expect("Failed to initialize Wayland backend"); + #[cfg(not(feature = "wayland"))] + panic!("wayland feature is not enabled"); } _ => panic!( "Unknown environment variable value for {}, try one of `x11`,`wayland`", @@ -552,16 +549,23 @@ impl EventLoop { } } + #[cfg(feature = "wayland")] let wayland_err = match EventLoop::new_wayland_any_thread() { Ok(event_loop) => return event_loop, Err(err) => err, }; + #[cfg(feature = "x11")] let x11_err = match EventLoop::new_x11_any_thread() { Ok(event_loop) => return event_loop, Err(err) => err, }; + #[cfg(not(feature = "wayland"))] + let wayland_err = "backend disabled"; + #[cfg(not(feature = "x11"))] + let x11_err = "backend disabled"; + let err_string = format!( "Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}", wayland_err, x11_err, @@ -569,22 +573,26 @@ impl EventLoop { panic!(err_string); } + #[cfg(feature = "wayland")] pub fn new_wayland() -> Result, ConnectError> { assert_is_main_thread("new_wayland_any_thread"); EventLoop::new_wayland_any_thread() } + #[cfg(feature = "wayland")] pub fn new_wayland_any_thread() -> Result, ConnectError> { 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> { let xconn = match X11_BACKEND.lock().as_ref() { Ok(xconn) => xconn.clone(), @@ -597,11 +605,13 @@ impl EventLoop { #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { + #[cfg(feature = "wayland")] EventLoop::Wayland(ref evlp) => evlp .available_monitors() .into_iter() .map(MonitorHandle::Wayland) .collect(), + #[cfg(feature = "x11")] EventLoop::X(ref evlp) => evlp .x_connection() .available_monitors() @@ -614,57 +624,46 @@ impl EventLoop { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { match *self { + #[cfg(feature = "wayland")] EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), + #[cfg(feature = "x11")] EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), } } pub fn create_proxy(&self) -> EventLoopProxy { - match *self { - EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()), - EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } pub fn run_return(&mut self, callback: F) where F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - match *self { - EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback), - EventLoop::X(ref mut evlp) => evlp.run_return(callback), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback)) } pub fn run(self, callback: F) -> ! where F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - match self { - EventLoop::Wayland(evlp) => evlp.run(callback), - EventLoop::X(evlp) => evlp.run(callback), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { - match *self { - EventLoop::Wayland(ref evl) => evl.window_target(), - EventLoop::X(ref evl) => evl.window_target(), - } + x11_or_wayland!(match self; EventLoop(evl) => evl.window_target()) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - match *self { - EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event), - EventLoopProxy::X(ref proxy) => proxy.send_event(event), - } + x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) } } pub enum EventLoopWindowTarget { + #[cfg(feature = "wayland")] Wayland(wayland::EventLoopWindowTarget), + #[cfg(feature = "x11")] X(x11::EventLoopWindowTarget), } @@ -672,8 +671,10 @@ impl EventLoopWindowTarget { #[inline] pub fn is_wayland(&self) -> bool { match *self { + #[cfg(feature = "wayland")] EventLoopWindowTarget::Wayland(_) => true, - EventLoopWindowTarget::X(_) => false, + #[cfg(feature = "x11")] + _ => false, } } } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 3262fe1e59..737fcc7862 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -554,6 +554,7 @@ impl EventLoop { let instant_wakeup = { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; let dispatched = window_target @@ -662,6 +663,7 @@ impl EventLoop { { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; window_target.store.lock().unwrap().for_each_redraw_trigger( @@ -689,6 +691,7 @@ impl EventLoop { { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; @@ -803,6 +806,7 @@ impl EventLoop { fn get_target(target: &RootELW) -> &EventLoopWindowTarget { match target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), } } diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 5b680999a6..a0be1e9d7c 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -158,6 +158,7 @@ impl Window { Some(Fullscreen::Borderless(RootMonitorHandle { inner: PlatformMonitorHandle::Wayland(ref monitor_id), })) => frame.set_fullscreen(Some(&monitor_id.proxy)), + #[cfg(feature = "x11")] Some(Fullscreen::Borderless(_)) => unreachable!(), None => { if attributes.maximized { @@ -354,6 +355,7 @@ impl Window { .unwrap() .set_fullscreen(Some(&monitor_id.proxy)); } + #[cfg(feature = "x11")] Some(Fullscreen::Borderless(_)) => unreachable!(), None => self.frame.lock().unwrap().unset_fullscreen(), } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 93a386f222..186e9c01fe 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -426,6 +426,7 @@ impl EventLoop { pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget { match target.p { super::EventLoopWindowTarget::X(ref target) => target, + #[cfg(feature = "wayland")] _ => unreachable!(), } } @@ -493,9 +494,21 @@ impl<'a> Deref for DeviceInfo<'a> { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(ffi::Window); +impl WindowId { + pub unsafe fn dummy() -> Self { + WindowId(0) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId(0) + } +} + pub struct Window(Arc); impl Deref for Window { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f4cb3d4536..a7e3198001 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -653,6 +653,7 @@ impl UnownedWindow { Fullscreen::Borderless(RootMonitorHandle { inner: PlatformMonitorHandle::X(ref monitor), }) => (None, monitor), + #[cfg(feature = "wayland")] _ => unreachable!(), }; From 4b1b314ce20e7f9a504cf920c5367265a6451b70 Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 15 Jun 2020 12:49:09 -0700 Subject: [PATCH 34/56] Test x11 and wayland features on CI --- .github/workflows/ci.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b98dbc56b1..1c35451427 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: wayland } - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } @@ -50,6 +52,7 @@ jobs: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" + OPTIONS: ${{ matrix.platform.options }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }} CMD: ${{ matrix.platform.cmd }} @@ -82,35 +85,35 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build shell: bash - run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build tests shell: bash - run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Run tests shell: bash if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build with serde enabled shell: bash - run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES - name: Build tests with serde enabled shell: bash - run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES - name: Run tests with serde enabled shell: bash if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES From bf62103417039827a9541f1c74793bc071295e19 Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Wed, 17 Jun 2020 15:55:52 +0200 Subject: [PATCH 35/56] Android run return (#1604) * Initial Draft * Minor clean up * cargo fmt * Removed accidental change * Update CHANGELOG.md Co-authored-by: VZout <=> --- CHANGELOG.md | 1 + src/platform/desktop.rs | 1 + src/platform_impl/android/mod.rs | 137 ++++++++++++++++++++++--------- 3 files changed, 100 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c586be8d49..0888252c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. +- On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. # 0.22.2 (2020-05-16) diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index df80143162..abf58b29ad 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -1,6 +1,7 @@ #![cfg(any( target_os = "windows", target_os = "macos", + target_os = "android", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index ce26994b5d..8a5c69409a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -44,6 +44,20 @@ fn poll(poll: Poll) -> Option { pub struct EventLoop { window_target: event_loop::EventLoopWindowTarget, user_queue: Arc>>, + first_event: Option, + start_cause: event::StartCause, + looper: ThreadLooper, + running: bool, +} + +macro_rules! call_event_handler { + ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ + if $cf != ControlFlow::Exit { + $event_handler($event, $window_target, &mut $cf); + } else { + $event_handler($event, $window_target, &mut ControlFlow::Exit); + } + }}; } impl EventLoop { @@ -56,42 +70,61 @@ impl EventLoop { _marker: std::marker::PhantomData, }, user_queue: Default::default(), + first_event: None, + start_cause: event::StartCause::Init, + looper: ThreadLooper::for_thread().unwrap(), + running: false, } } - pub fn run(self, mut event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> ! where F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let mut cf = ControlFlow::default(); - let mut first_event = None; - let mut start_cause = event::StartCause::Init; - let looper = ThreadLooper::for_thread().unwrap(); - let mut running = false; - - loop { - event_handler( - event::Event::NewEvents(start_cause), + self.run_return(event_handler); + ::std::process::exit(0); + } + + pub fn run_return(&mut self, mut event_handler: F) + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let mut control_flow = ControlFlow::default(); + + 'event_loop: loop { + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::NewEvents(self.start_cause) ); let mut redraw = false; let mut resized = false; - match first_event.take() { + match self.first_event.take() { Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { Event::WindowCreated => { - event_handler(event::Event::Resumed, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Resumed + ); } Event::WindowResized => resized = true, Event::WindowRedrawNeeded => redraw = true, Event::WindowDestroyed => { - event_handler(event::Event::Suspended, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Suspended + ); } - Event::Pause => running = false, - Event::Resume => running = true, + Event::Pause => self.running = false, + Event::Resume => self.running = true, Event::ConfigChanged => { let am = ndk_glue::native_activity().asset_manager(); let config = Configuration::from_asset_manager(&am); @@ -107,7 +140,12 @@ impl EventLoop { scale_factor, }, }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event + ); } } _ => {} @@ -147,7 +185,12 @@ impl EventLoop { force: None, }), }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event + ); } } InputEvent::KeyEvent(_) => {} // TODO @@ -160,50 +203,66 @@ impl EventLoop { Some(EventSource::User) => { let mut user_queue = self.user_queue.lock().unwrap(); while let Some(event) = user_queue.pop_front() { - event_handler( - event::Event::UserEvent(event), + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::UserEvent(event) ); } } - None => {} + None => { + control_flow = ControlFlow::Exit; + } } - event_handler( - event::Event::MainEventsCleared, + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::MainEventsCleared ); - if resized && running { + if resized && self.running { let size = MonitorHandle.size(); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!(event_handler, self.window_target(), control_flow, event); } - if redraw && running { + if redraw && self.running { let event = event::Event::RedrawRequested(window::WindowId(WindowId)); - event_handler(event, self.window_target(), &mut cf); + call_event_handler!(event_handler, self.window_target(), control_flow, event); } - event_handler( - event::Event::RedrawEventsCleared, + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::RedrawEventsCleared ); - match cf { - ControlFlow::Exit => panic!(), + match control_flow { + ControlFlow::Exit => { + self.first_event = poll( + self.looper + .poll_once_timeout(Duration::from_millis(0)) + .unwrap(), + ); + self.start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }; + break 'event_loop; + } ControlFlow::Poll => { - start_cause = event::StartCause::Poll; + self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { - first_event = poll(looper.poll_all().unwrap()); - start_cause = event::StartCause::WaitCancelled { + self.first_event = poll(self.looper.poll_all().unwrap()); + self.start_cause = event::StartCause::WaitCancelled { start: Instant::now(), requested_resume: None, } @@ -215,8 +274,8 @@ impl EventLoop { } else { instant - start }; - first_event = poll(looper.poll_all_timeout(duration).unwrap()); - start_cause = if first_event.is_some() { + self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap()); + self.start_cause = if self.first_event.is_some() { event::StartCause::WaitCancelled { start, requested_resume: Some(instant), From 2191e9ecd59182e9418e53a3c0cb56421a4a6887 Mon Sep 17 00:00:00 2001 From: Andrey Lesnikov Date: Sat, 20 Jun 2020 03:42:19 +0300 Subject: [PATCH 36/56] macOS: Support click-dragging out of a window (#1607) * macos: Support click-dragging out of a window * macos: Use NSEvent::pressedMouseButtons for click-dragging * macos: Click-dragging: Move pressedMouseButtons inside --- src/platform_impl/macos/view.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 82ee50767e..d764902db0 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -915,8 +915,11 @@ fn mouse_motion(this: &Object, event: id) { || view_point.x > view_rect.size.width || view_point.y > view_rect.size.height { - // Point is outside of the client area (view) - return; + let mouse_buttons_down: NSInteger = msg_send![class!(NSEvent), pressedMouseButtons]; + if mouse_buttons_down == 0 { + // Point is outside of the client area (view) and no buttons are pressed + return; + } } let x = view_point.x as f64; From b1e22aa5597a57882e862ca03ef6440d9e5ec4dd Mon Sep 17 00:00:00 2001 From: Jurgis Date: Mon, 29 Jun 2020 01:17:27 +0300 Subject: [PATCH 37/56] Make drag and drop optional (fixes OleInitialize failure #1255) (#1524) Co-authored-by: Osspial --- CHANGELOG.md | 3 ++- src/platform/windows.rs | 14 ++++++++++++++ src/platform_impl/windows/event_loop.rs | 2 +- src/platform_impl/windows/mod.rs | 14 +++++++++++++- src/platform_impl/windows/window.rs | 13 ++++++++++--- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0888252c85..8c042e1369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - +- On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. @@ -23,6 +23,7 @@ - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling - On macOS, fix `EventLoopProxy` leaking memory for every instance. +- On Windows, drag and drop can now be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`. # 0.22.0 (2020-03-09) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a356f1e1c0..a040be363f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -117,6 +117,14 @@ pub trait WindowBuilderExtWindows { /// This sets `WS_EX_NOREDIRECTIONBITMAP`. fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder; + + /// Enables or disables drag and drop support (enabled by default). Will interfere with other crates + /// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of + /// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize + /// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. + /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. + /// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information. + fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; } impl WindowBuilderExtWindows for WindowBuilder { @@ -137,6 +145,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self.platform_specific.no_redirection_bitmap = flag; self } + + #[inline] + fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder { + self.platform_specific.drag_and_drop = flag; + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a7deac50c5..739990a67b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -82,7 +82,7 @@ lazy_static! { pub(crate) struct SubclassInput { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, - pub file_drop_handler: FileDropHandler, + pub file_drop_handler: Option, } impl SubclassInput { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 021c903b8d..498cdfb80c 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -14,11 +14,23 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; -#[derive(Clone, Default)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, + pub drag_and_drop: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + parent: None, + taskbar_icon: None, + no_redirection_bitmap: false, + drag_and_drop: true, + } + } } unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 45a3b5debd..0f4dcf8bb8 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -70,8 +70,9 @@ impl Window { // // done. you owe me -- ossi unsafe { + let drag_and_drop = pl_attr.drag_and_drop; init(w_attr, pl_attr, event_loop).map(|win| { - let file_drop_handler = { + let file_drop_handler = if drag_and_drop { use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK}; let ole_init_result = ole2::OleInitialize(ptr::null_mut()); @@ -80,7 +81,11 @@ impl Window { if ole_init_result == OLE_E_WRONGCOMPOBJ { panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); } else if ole_init_result == RPC_E_CHANGED_MODE { - panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); + panic!( + "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ + Make sure other crates are not using multithreaded COM library \ + on the same thread or disable drag and drop support." + ); } let file_drop_runner = event_loop.runner_shared.clone(); @@ -99,7 +104,9 @@ impl Window { ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK ); - file_drop_handler + Some(file_drop_handler) + } else { + None }; let subclass_input = event_loop::SubclassInput { From dd866a74a690d37fdd11385741dee06e9f7cd02f Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 2 Jul 2020 16:53:47 -0400 Subject: [PATCH 38/56] On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops (#1615) --- CHANGELOG.md | 2 + src/platform_impl/windows/event_loop.rs | 29 +++++++---- .../windows/event_loop/runner.rs | 49 ++++++++++++++++--- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c042e1369..d28b32c3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased + - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas @@ -7,6 +8,7 @@ - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. +- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 739990a67b..61ec167fc7 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -226,7 +226,7 @@ impl EventLoop { } unsafe { - runner.call_event_handler(Event::LoopDestroyed); + runner.loop_destroyed(); } runner.reset_runner(); } @@ -1933,14 +1933,25 @@ unsafe extern "system" fn thread_event_target_callback( // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. if subclass_input.event_loop_runner.handling_events() { - // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` - // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages( - None, - &subclass_input.event_loop_runner - )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + if subclass_input.event_loop_runner.should_buffer() { + // This branch can be triggered when a nested win32 event loop is triggered + // inside of the `event_handler` callback. + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } else { + // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` + // doesn't call WM_PAINT for the thread event target (i.e. this window). + assert!(flush_paint_messages( + None, + &subclass_input.event_loop_runner + )); + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); + } } 0 diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index 258a40c08c..4f90966c43 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -53,6 +53,8 @@ enum RunnerState { /// The event loop is handling the redraw events and sending them to the user's callback. /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. HandlingRedrawEvents, + /// The event loop has been destroyed. No other events will be emitted. + Destroyed, } enum BufferedEvent { @@ -229,7 +231,11 @@ impl EventLoopRunner { self.move_state_to(RunnerState::Idle); } - pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) { + pub(crate) unsafe fn loop_destroyed(&self) { + self.move_state_to(RunnerState::Destroyed); + } + + unsafe fn call_event_handler(&self, event: Event<'_, T>) { self.catch_unwind(|| { let mut control_flow = self.control_flow.take(); let mut event_handler = self.event_handler.take() @@ -260,8 +266,8 @@ impl EventLoopRunner { } } - /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as - /// necessary to bring the internal `RunnerState` to the new runner state. + /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, `RedrawEventsCleared`, and + /// `LoopDestroyed`) as necessary to bring the internal `RunnerState` to the new runner state. /// /// The state transitions are defined as follows: /// @@ -273,14 +279,20 @@ impl EventLoopRunner { /// ^ | /// | V /// Idle <--- HandlingRedrawEvents + /// | + /// V + /// Destroyed /// ``` /// - /// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to - /// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state - /// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and + /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to + /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current + /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the + /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. unsafe fn move_state_to(&self, new_runner_state: RunnerState) { - use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; + use RunnerState::{ + Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized, + }; match ( self.runner_state.replace(new_runner_state), @@ -289,7 +301,8 @@ impl EventLoopRunner { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) - | (HandlingRedrawEvents, HandlingRedrawEvents) => (), + | (HandlingRedrawEvents, HandlingRedrawEvents) + | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { @@ -304,6 +317,12 @@ impl EventLoopRunner { self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } + (Uninitialized, Destroyed) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } (_, Uninitialized) => panic!("cannot move state to Uninitialized"), // State transitions that start the event handling process. @@ -314,6 +333,9 @@ impl EventLoopRunner { self.call_new_events(false); self.call_event_handler(Event::MainEventsCleared); } + (Idle, Destroyed) => { + self.call_event_handler(Event::LoopDestroyed); + } (HandlingMainEvents, HandlingRedrawEvents) => { self.call_event_handler(Event::MainEventsCleared); @@ -323,6 +345,11 @@ impl EventLoopRunner { self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } + (HandlingMainEvents, Destroyed) => { + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } (HandlingRedrawEvents, Idle) => { self.call_redraw_events_cleared(); @@ -332,6 +359,12 @@ impl EventLoopRunner { self.call_redraw_events_cleared(); self.call_new_events(false); } + (HandlingRedrawEvents, Destroyed) => { + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } + + (Destroyed, _) => panic!("cannot move state from Destroyed"), } } From 3d5d05eac7d23c78e4e66789563b3d4285e3a4eb Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux Date: Sat, 4 Jul 2020 15:46:41 -0400 Subject: [PATCH 39/56] Move available_monitors and primary_monitor to EventLoopWindowTarget (#1616) --- CHANGELOG.md | 1 + src/event_loop.rs | 20 +++--- src/monitor.rs | 4 +- src/platform_impl/android/mod.rs | 22 ++++--- src/platform_impl/ios/event_loop.rs | 22 ++++--- src/platform_impl/linux/mod.rs | 62 ++++++++++--------- src/platform_impl/linux/wayland/event_loop.rs | 23 +++---- src/platform_impl/linux/x11/mod.rs | 4 -- src/platform_impl/macos/event_loop.rs | 22 ++++--- src/platform_impl/web/event_loop/mod.rs | 8 --- .../web/event_loop/window_target.rs | 11 +++- src/platform_impl/windows/event_loop.rs | 15 ++++- src/platform_impl/windows/monitor.rs | 13 +--- src/window.rs | 4 +- 14 files changed, 121 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28b32c3f9..2fb9be9ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas diff --git a/src/event_loop.rs b/src/event_loop.rs index 224292a564..a523c01aed 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -155,11 +155,20 @@ impl EventLoop { event_loop_proxy: self.event_loop.create_proxy(), } } +} + +impl Deref for EventLoop { + type Target = EventLoopWindowTarget; + fn deref(&self) -> &EventLoopWindowTarget { + self.event_loop.window_target() + } +} +impl EventLoopWindowTarget { /// Returns the list of all the monitors available on the system. #[inline] pub fn available_monitors(&self) -> impl Iterator { - self.event_loop + self.p .available_monitors() .into_iter() .map(|inner| MonitorHandle { inner }) @@ -169,18 +178,11 @@ impl EventLoop { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle { - inner: self.event_loop.primary_monitor(), + inner: self.p.primary_monitor(), } } } -impl Deref for EventLoop { - type Target = EventLoopWindowTarget; - fn deref(&self) -> &EventLoopWindowTarget { - self.event_loop.window_target() - } -} - /// Used to send custom events to `EventLoop`. pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy, diff --git a/src/monitor.rs b/src/monitor.rs index 8977c11a4a..126b03e2bf 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -3,11 +3,11 @@ //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] //! type. This is retreived from one of the following methods, which return an iterator of //! [`MonitorHandle`][monitor_handle]: -//! - [`EventLoop::available_monitors`][loop_get] +//! - [`EventLoopWindowTarget::available_monitors`][loop_get] //! - [`Window::available_monitors`][window_get]. //! //! [monitor_handle]: crate::monitor::MonitorHandle -//! [loop_get]: crate::event_loop::EventLoop::available_monitors +//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors //! [window_get]: crate::window::Window::available_monitors use crate::{ dpi::{PhysicalPosition, PhysicalSize}, diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 8a5c69409a..d42b942083 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -295,16 +295,6 @@ impl EventLoop { &self.window_target } - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn available_monitors(&self) -> VecDeque { - let mut v = VecDeque::with_capacity(1); - v.push_back(self.primary_monitor()); - v - } - pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { queue: self.user_queue.clone(), @@ -339,6 +329,18 @@ pub struct EventLoopWindowTarget { _marker: std::marker::PhantomData, } +impl EventLoopWindowTarget { + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(self.primary_monitor()); + v + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId; diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 61b389d43a..2e7c64d2a3 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -49,6 +49,18 @@ pub struct EventLoopWindowTarget { sender_to_clone: Sender, } +impl EventLoopWindowTarget { + pub fn available_monitors(&self) -> VecDeque { + // guaranteed to be on main thread + unsafe { monitor::uiscreens() } + } + + pub fn primary_monitor(&self) -> MonitorHandle { + // guaranteed to be on main thread + unsafe { monitor::main_uiscreen() } + } +} + pub struct EventLoop { window_target: RootEventLoopWindowTarget, } @@ -115,16 +127,6 @@ impl EventLoop { EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) } - pub fn available_monitors(&self) -> VecDeque { - // guaranteed to be on main thread - unsafe { monitor::uiscreens() } - } - - pub fn primary_monitor(&self) -> MonitorHandle { - // guaranteed to be on main thread - unsafe { monitor::main_uiscreen() } - } - pub fn window_target(&self) -> &RootEventLoopWindowTarget { &self.window_target } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 613d0e231b..0fee68324e 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -602,35 +602,6 @@ impl EventLoop { Ok(EventLoop::X(x11::EventLoop::new(xconn))) } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - match *self { - #[cfg(feature = "wayland")] - EventLoop::Wayland(ref evlp) => evlp - .available_monitors() - .into_iter() - .map(MonitorHandle::Wayland) - .collect(), - #[cfg(feature = "x11")] - EventLoop::X(ref evlp) => evlp - .x_connection() - .available_monitors() - .into_iter() - .map(MonitorHandle::X) - .collect(), - } - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - match *self { - #[cfg(feature = "wayland")] - EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), - #[cfg(feature = "x11")] - EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), - } - } - pub fn create_proxy(&self) -> EventLoopProxy { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } @@ -677,6 +648,39 @@ impl EventLoopWindowTarget { _ => false, } } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(ref evlp) => evlp + .available_monitors() + .into_iter() + .map(MonitorHandle::Wayland) + .collect(), + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref evlp) => evlp + .x_connection() + .available_monitors() + .into_iter() + .map(MonitorHandle::X) + .collect(), + } + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(ref evlp) => { + MonitorHandle::Wayland(evlp.primary_monitor()) + } + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref evlp) => { + MonitorHandle::X(evlp.x_connection().primary_monitor()) + } + } + } } fn sticky_exit_callback( diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 737fcc7862..77215b9ce9 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -245,8 +245,6 @@ pub struct EventLoop { poll: Poll, // The wayland display pub display: Arc, - // The output manager - pub outputs: OutputMgr, // The cursor manager cursor_manager: Arc>, kbd_channel: Receiver>, @@ -277,6 +275,8 @@ pub struct EventLoopWindowTarget { pub display: Arc, // The list of seats pub seats: Arc>>, + // The output manager + pub outputs: OutputMgr, _marker: ::std::marker::PhantomData, } @@ -418,10 +418,10 @@ impl EventLoop { .unwrap(); let cursor_manager_clone = cursor_manager.clone(); + let outputs = env.outputs.clone(); Ok(EventLoop { poll, display: display.clone(), - outputs: env.outputs.clone(), user_sender, user_channel, kbd_channel, @@ -435,6 +435,7 @@ impl EventLoop { cleanup_needed: Arc::new(Mutex::new(false)), seats, display, + outputs, _marker: ::std::marker::PhantomData, }), _marker: ::std::marker::PhantomData, @@ -633,14 +634,6 @@ impl EventLoop { callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); } - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor(&self.outputs) - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - pub fn window_target(&self) -> &RootELW { &self.window_target } @@ -650,6 +643,14 @@ impl EventLoopWindowTarget { pub fn display(&self) -> &Display { &*self.display } + + pub fn available_monitors(&self) -> VecDeque { + available_monitors(&self.outputs) + } + + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor(&self.outputs) + } } /* diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 186e9c01fe..0849c0f919 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -258,10 +258,6 @@ impl EventLoop { &self.target } - pub(crate) fn x_connection(&self) -> &Arc { - get_xtarget(&self.target).x_connection() - } - pub fn run_return(&mut self, mut callback: F) where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 01437b845e..0d88d97984 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -34,6 +34,18 @@ impl Default for EventLoopWindowTarget { } } +impl EventLoopWindowTarget { + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() + } +} + pub struct EventLoop { window_target: Rc>, _delegate: IdRef, @@ -68,16 +80,6 @@ impl EventLoop { } } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - monitor::available_monitors() - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - monitor::primary_monitor() - } - pub fn window_target(&self) -> &RootWindowTarget { &self.window_target } diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 50640a1faf..fa01b5df05 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -27,14 +27,6 @@ impl EventLoop { } } - pub fn available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() - } - - pub fn primary_monitor(&self) -> monitor::Handle { - monitor::Handle - } - pub fn run(self, mut event_handler: F) -> ! where F: 'static diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index db485a16d3..3ba5e1d2b5 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,9 +1,10 @@ -use super::{backend, device, proxy::Proxy, runner, window}; +use super::{super::monitor, backend, device, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::{Theme, WindowId}; use std::clone::Clone; +use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; pub struct WindowTarget { pub(crate) runner: runner::Shared, @@ -213,4 +214,12 @@ impl WindowTarget { }); }); } + + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn primary_monitor(&self) -> monitor::Handle { + monitor::Handle + } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 61ec167fc7..eb0a65762b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -4,6 +4,7 @@ mod runner; use parking_lot::Mutex; use std::{ + collections::VecDeque, marker::PhantomData, mem, panic, ptr, rc::Rc, @@ -38,7 +39,8 @@ use crate::{ dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, - monitor, raw_input, util, + monitor::{self, MonitorHandle}, + raw_input, util, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, @@ -247,6 +249,15 @@ impl EventLoopWindowTarget { target_window: self.thread_msg_target, } } + + // TODO: Investigate opportunities for caching + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() + } } fn main_thread_id() -> DWORD { @@ -851,7 +862,7 @@ unsafe extern "system" fn public_window_callback( window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; } *fullscreen_monitor = crate::monitor::MonitorHandle { - inner: monitor::MonitorHandle::new(new_monitor), + inner: MonitorHandle::new(new_monitor), }; } } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 7136f35dfe..7fa4c73dc5 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -11,7 +11,7 @@ use std::{ io, mem, ptr, }; -use super::{util, EventLoop}; +use super::util; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, @@ -126,17 +126,6 @@ pub fn current_monitor(hwnd: HWND) -> MonitorHandle { MonitorHandle::new(hmonitor) } -impl EventLoop { - // TODO: Investigate opportunities for caching - pub fn available_monitors(&self) -> VecDeque { - available_monitors() - } - - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor() - } -} - impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() diff --git a/src/window.rs b/src/window.rs index 113e7c042b..000adef735 100644 --- a/src/window.rs +++ b/src/window.rs @@ -758,7 +758,7 @@ impl Window { /// Returns the list of all the monitors available on the system. /// - /// This is the same as `EventLoop::available_monitors`, and is provided for convenience. + /// This is the same as `EventLoopWindowTarget::available_monitors`, and is provided for convenience. /// /// ## Platform-specific /// @@ -773,7 +773,7 @@ impl Window { /// Returns the primary monitor of the system. /// - /// This is the same as `EventLoop::primary_monitor`, and is provided for convenience. + /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. /// /// ## Platform-specific /// From 6919c2fb2d45dc4063c9ef52a69d857ed30657c7 Mon Sep 17 00:00:00 2001 From: Matt Kraai Date: Thu, 9 Jul 2020 15:08:26 +0000 Subject: [PATCH 40/56] Fix misspellings in comments (#1618) --- src/lib.rs | 2 +- src/monitor.rs | 2 +- src/platform_impl/linux/x11/window.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce66665616..4a45022c36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. //! -//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will +//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! will run until the `control_flow` argument given to the closure is set to //! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the diff --git a/src/monitor.rs b/src/monitor.rs index 126b03e2bf..5cf680ff21 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,7 +1,7 @@ //! Types useful for interacting with a user's monitors. //! //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] -//! type. This is retreived from one of the following methods, which return an iterator of +//! type. This is retrieved from one of the following methods, which return an iterator of //! [`MonitorHandle`][monitor_handle]: //! - [`EventLoopWindowTarget::available_monitors`][loop_get] //! - [`Window::available_monitors`][window_get]. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index a7e3198001..ae63f436a4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -227,7 +227,7 @@ impl UnownedWindow { // is > 0, like we do in glutin. // // It is non obvious which masks, if any, we should pass to - // `XGetVisualInfo`. winit doesn't recieve any info about what + // `XGetVisualInfo`. winit doesn't receive any info about what // properties the user wants. Users should consider choosing the // visual themselves as glutin does. match pl_attribs.visual_infos { From 55dff53a98097824db0460d9d7083a842f923783 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Jul 2020 21:13:17 +0000 Subject: [PATCH 41/56] Fix Window platform support documentation This resolves various problems with the documentation about platform support on the Window struct. It also completely removes pointless runtime errors in favor of consistent no-ops on all platforms that do not support a certain features. --- CHANGELOG.md | 4 ++ src/platform_impl/android/mod.rs | 4 +- src/platform_impl/ios/window.rs | 2 +- src/platform_impl/web/window.rs | 6 +-- src/platform_impl/windows/window.rs | 2 +- src/window.rs | 60 ++++++++++++----------------- 6 files changed, 35 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb9be9ec4..9eba304dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,10 @@ - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. +- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. +- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. +- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index d42b942083..e4bdef8229 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -419,7 +419,7 @@ impl Window { } pub fn set_inner_size(&self, _size: Size) { - panic!("Cannot set window size on Android"); + warn!("Cannot set window size on Android"); } pub fn outer_size(&self) -> PhysicalSize { @@ -441,7 +441,7 @@ impl Window { pub fn set_maximized(&self, _maximized: bool) {} pub fn set_fullscreen(&self, _monitor: Option) { - panic!("Cannot set fullscreen on Android"); + warn!("Cannot set fullscreen on Android"); } pub fn fullscreen(&self) -> Option { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index ec943116db..5fa83e9027 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -142,7 +142,7 @@ impl Inner { } pub fn set_inner_size(&self, _size: Size) { - unimplemented!("not clear what `Window::set_inner_size` means on iOS"); + warn!("not clear what `Window::set_inner_size` means on iOS"); } pub fn set_min_inner_size(&self, _dimensions: Option) { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index bd525056fa..58d551354b 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -171,14 +171,12 @@ impl Window { #[inline] pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { - // Intentionally a no-op, as the web does not support setting cursor positions - Ok(()) + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - // Intentionally a no-op, as the web does not (properly) support grabbing the cursor - Ok(()) + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0f4dcf8bb8..3b8dc98f51 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -609,7 +609,7 @@ impl Window { #[inline] pub fn set_ime_position(&self, _position: Position) { - unimplemented!(); + warn!("`Window::set_ime_position` is ignored on Windows") } #[inline] diff --git a/src/window.rs b/src/window.rs index 000adef735..d6b55830e9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -401,6 +401,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Unsupported. #[inline] pub fn request_redraw(&self) { self.window.request_redraw() @@ -420,6 +421,7 @@ impl Window { /// window's [safe area] in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the /// same value as `outer_position`._ + /// - **Android / Wayland:** Always returns [`NotSupportedError`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] @@ -442,6 +444,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. + /// - **Android / Wayland:** Always returns [`NotSupportedError`]. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -457,6 +460,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Sets the top-left coordinates relative to the viewport. + /// - **Android / Wayland:** Unsupported. #[inline] pub fn set_outer_position>(&self, position: P) { self.window.set_outer_position(position.into()) @@ -485,8 +489,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` - /// would mean for iOS. + /// - **iOS / Android:** Unsupported. /// - **Web:** Sets the size of the canvas element. #[inline] pub fn set_inner_size>(&self, size: S) { @@ -513,8 +516,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { self.window.set_min_inner_size(min_size.map(|s| s.into())) @@ -524,8 +526,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Andraid / Web:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { self.window.set_max_inner_size(max_size.map(|s| s.into())) @@ -538,7 +539,7 @@ impl Window { /// /// ## Platform-specific /// - /// - Has no effect on iOS. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_title(&self, title: &str) { self.window.set_title(title) @@ -549,9 +550,8 @@ impl Window { /// If `false`, this will hide the window. If `true`, this will show the window. /// ## Platform-specific /// - /// - **Android:** Has no effect. + /// - **Android / Wayland / Web:** Unsupported. /// - **iOS:** Can only be called on the main thread. - /// - **Web:** Has no effect. #[inline] pub fn set_visible(&self, visible: bool) { self.window.set_visible(visible) @@ -570,8 +570,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) @@ -581,7 +580,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect + /// - **iOS / Android / Web:** Unsupported. + /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { self.window.set_minimized(minimized); @@ -591,8 +591,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) @@ -617,6 +616,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode. /// - **Windows:** Screen saver is disabled in fullscreen mode. + /// - **Android:** Unsupported. #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window.set_fullscreen(fullscreen) @@ -627,6 +627,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Will always return `None`. #[inline] pub fn fullscreen(&self) -> Option { self.window.fullscreen() @@ -635,9 +636,8 @@ impl Window { /// Turn window decorations on or off. /// /// ## Platform-specific - /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden - /// via [`setPrefersStatusBarHidden`]. - /// - **Web:** Has no effect. + /// + /// - **iOS / Android / Web:** Unsupported. /// /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[inline] @@ -649,8 +649,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland:** Unsupported. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -661,7 +660,7 @@ impl Window { /// /// ## Platform-specific /// - /// This only has an effect on Windows and X11. + /// - **iOS / Android / Web / Wayland / macOS:** Unsupported. /// /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. @@ -677,8 +676,7 @@ impl Window { /// /// ## Platform-specific /// - /// **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. #[inline] pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) @@ -691,8 +689,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Android:** Has no effect. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window.set_cursor_icon(cursor); @@ -702,8 +699,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Always returns an `Err`. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { self.window.set_cursor_position(position.into()) @@ -713,13 +709,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually - /// awkward. - /// - **Wayland:** This presently merely locks the cursor in a fixed location, which looks visually - /// awkward. - /// - **Android:** Has no effect. - /// - **iOS:** Always returns an Err. - /// - **Web:** Has no effect. + /// - **macOS / Wayland:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { self.window.set_cursor_grab(grab) @@ -736,8 +727,7 @@ impl Window { /// - **Wayland:** The cursor is only hidden within the confines of the window. /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is /// outside of the window. - /// - **iOS:** Has no effect. - /// - **Android:** Has no effect. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window.set_cursor_visible(visible) From 40232d48ba3c80ba076ea1dc1fbb7d1c6fc80aac Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Jul 2020 22:16:21 +0000 Subject: [PATCH 42/56] Use `PhysicalPosition` in `PixelDelta` event This removes the `LogicalPosition` from the `PixelDelta`, since all other APIs have been switched to use `PhysicalPosition` instead. Fixes #1406. --- CHANGELOG.md | 9 +++++---- src/event.rs | 4 ++-- src/platform_impl/linux/wayland/pointer.rs | 21 +++++++++++---------- src/platform_impl/macos/view.rs | 6 +++++- src/platform_impl/web/stdweb/event.rs | 5 ++++- src/platform_impl/web/web_sys/event.rs | 5 ++++- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eba304dd3..dc9a6b3809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. +- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. +- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. +- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. +- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. # 0.22.2 (2020-05-16) @@ -49,10 +54,6 @@ - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. -- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. -- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. -- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. -- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. # 0.21.0 (2020-02-04) diff --git a/src/event.rs b/src/event.rs index 47119fa807..9e9320c2da 100644 --- a/src/event.rs +++ b/src/event.rs @@ -37,7 +37,7 @@ use instant::Instant; use std::path::PathBuf; use crate::{ - dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize}, platform_impl, window::{Theme, WindowId}, }; @@ -764,7 +764,7 @@ pub enum MouseScrollDelta { /// Scroll events are expressed as a PixelDelta if /// supported by the device (eg. a touchpad) and /// platform. - PixelDelta(LogicalPosition), + PixelDelta(PhysicalPosition), } /// Symbolic name for a keyboard key. diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index 5f93099f4a..4f2f4c2eaa 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -170,14 +170,15 @@ pub fn implement_pointer( wl_pointer::Axis::HorizontalScroll => x += value as f32, _ => unreachable!(), } + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let delta = LogicalPosition::new(x as f64, y as f64) + .to_physical(scale_factor); sink.send_window_event( WindowEvent::MouseWheel { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::PixelDelta( - (x as f64, y as f64).into(), - ), + delta: MouseScrollDelta::PixelDelta(delta), phase: TouchPhase::Moved, modifiers: modifiers_tracker.lock().unwrap().clone(), }, @@ -210,21 +211,21 @@ pub fn implement_pointer( device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::LineDelta(x as f32, y as f32), + delta: MouseScrollDelta::LineDelta(x, y), phase: axis_state, modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, ); } else if let Some((x, y)) = axis_buffer { + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let delta = LogicalPosition::new(x, y).to_physical(scale_factor); sink.send_window_event( WindowEvent::MouseWheel { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::PixelDelta( - (x as f64, y as f64).into(), - ), + delta: MouseScrollDelta::PixelDelta(delta), phase: axis_state, modifiers: modifiers_tracker.lock().unwrap().clone(), }, @@ -238,11 +239,11 @@ pub fn implement_pointer( axis_state = TouchPhase::Ended; } PtrEvent::AxisDiscrete { axis, discrete } => { - let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0)); + let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0.0, 0.0)); match axis { // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= discrete, - wl_pointer::Axis::HorizontalScroll => x += discrete, + wl_pointer::Axis::VerticalScroll => y -= discrete as f32, + wl_pointer::Axis::HorizontalScroll => x += discrete as f32, _ => unreachable!(), } axis_discrete_buffer = Some((x, y)); diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index d764902db0..357515007a 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -999,10 +999,14 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let delta = { let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); if event.hasPreciseScrollingDeltas() == YES { - MouseScrollDelta::PixelDelta((x as f64, y as f64).into()) + let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); + MouseScrollDelta::PixelDelta(delta) } else { MouseScrollDelta::LineDelta(x as f32, y as f32) } diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 8c534dc053..8464c616bb 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -36,7 +36,10 @@ pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { match event.delta_mode() { MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + MouseWheelDeltaMode::Pixel => { + let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); + Some(MouseScrollDelta::PixelDelta(delta)) + } MouseWheelDeltaMode::Page => None, } } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 1c1bdba41c..a06418410c 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -35,7 +35,10 @@ pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + WheelEvent::DOM_DELTA_PIXEL => { + let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); + Some(MouseScrollDelta::PixelDelta(delta)) + } _ => None, } } From 7a49c88200fe2a9559c8cc9098f38e209567f691 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 1 Aug 2020 23:10:33 +0000 Subject: [PATCH 43/56] Fix with_fullscreen signature --- src/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index d6b55830e9..a00cd7c028 100644 --- a/src/window.rs +++ b/src/window.rs @@ -251,8 +251,8 @@ impl WindowBuilder { /// /// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen #[inline] - pub fn with_fullscreen(mut self, monitor: Option) -> Self { - self.window.fullscreen = monitor; + pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { + self.window.fullscreen = fullscreen; self } From 05fdcb5b270062b4bc85d3433ed12551f8a6741d Mon Sep 17 00:00:00 2001 From: josh65536 Date: Tue, 4 Aug 2020 21:39:09 -0400 Subject: [PATCH 44/56] Web: Use mouse events instead of pointer events if the latter isn't supported (#1630) * Fixed Safari not getting mouse events * Edited changelog * Addressed compiler warnings Co-authored-by: Ryan G --- CHANGELOG.md | 1 + src/platform_impl/web/web_sys/canvas.rs | 146 ++++++++++++++++++------ 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9a6b3809..d9257f7afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. +- On Web, use mouse events if pointer events aren't supported. This affects Safari. - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index e8fd3a2bba..617f6ed61a 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,8 +9,8 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent, - WheelEvent, + Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, + PointerEvent, WheelEvent, }; pub struct Canvas { @@ -24,8 +24,14 @@ pub struct Canvas { on_cursor_leave: Option>, on_cursor_enter: Option>, on_cursor_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, + // Fallback events when pointer event support is missing + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, wants_fullscreen: Rc>, @@ -76,8 +82,13 @@ impl Canvas { on_cursor_leave: None, on_cursor_enter: None, on_cursor_move: None, - on_mouse_release: None, + on_pointer_release: None, + on_pointer_press: None, + on_mouse_leave: None, + on_mouse_enter: None, + on_mouse_move: None, on_mouse_press: None, + on_mouse_release: None, on_mouse_wheel: None, on_fullscreen_change: None, wants_fullscreen: Rc::new(RefCell::new(false)), @@ -180,63 +191,110 @@ impl Canvas { where F: 'static + FnMut(i32), { - self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); - })); + if has_pointer_event() { + self.on_cursor_leave = + Some(self.add_event("pointerout", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } else { + self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| { + handler(0); + })); + } } pub fn on_cursor_enter(&mut self, mut handler: F) where F: 'static + FnMut(i32), { - self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); - })); + if has_pointer_event() { + self.on_cursor_enter = + Some(self.add_event("pointerover", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } else { + self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| { + handler(0); + })); + } } pub fn on_mouse_release(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_user_event( - "pointerup", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); + if has_pointer_event() { + self.on_pointer_release = Some(self.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } else { + self.on_mouse_release = + Some(self.add_user_event("mouseup", move |event: MouseEvent| { + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_mouse_press(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_user_event( - "pointerdown", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); + if has_pointer_event() { + self.on_pointer_press = Some(self.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } else { + self.on_mouse_press = + Some(self.add_user_event("mousedown", move |event: MouseEvent| { + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_cursor_move(&mut self, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { - self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); + if has_pointer_event() { + self.on_cursor_move = + Some(self.add_event("pointermove", move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), + event::mouse_modifiers(&event), + ); + })); + } else { + self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| { + handler( + 0, + event::mouse_position(&event).to_physical(super::scale_factor()), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_mouse_wheel(&mut self, mut handler: F) @@ -334,3 +392,15 @@ impl Canvas { super::is_fullscreen(&self.raw) } } + +/// Returns whether pointer events are supported. +/// Used to decide whether to use pointer events +/// or plain mouse events. Note that Safari +/// doesn't support pointer events now. +fn has_pointer_event() -> bool { + if let Some(window) = web_sys::window() { + window.get("PointerEvent").is_some() + } else { + false + } +} From 68100102be588aea3dd42cb64647e1f8624615b2 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Wed, 12 Aug 2020 20:56:28 +0200 Subject: [PATCH 45/56] android: fix event loop polling (#1638) * android: poll looper for ControlFlow::Poll and don't exit when no new event received * Add Android ControlFlow:Poll fix to CHANGELOG --- CHANGELOG.md | 1 + src/platform_impl/android/mod.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9257f7afe..ae81ae6f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- On Android, fix `ControlFlow::Poll` not polling the Android event queue. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index e4bdef8229..453815d872 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -211,9 +211,7 @@ impl EventLoop { ); } } - None => { - control_flow = ControlFlow::Exit; - } + None => {} } call_event_handler!( @@ -258,6 +256,11 @@ impl EventLoop { break 'event_loop; } ControlFlow::Poll => { + self.first_event = poll( + self.looper + .poll_all_timeout(Duration::from_millis(0)) + .unwrap(), + ); self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { From 514ab043f21447ec1fd6f5cc09ee9b5e65663517 Mon Sep 17 00:00:00 2001 From: TakWolf <6064962+TakWolf@users.noreply.github.com> Date: Fri, 14 Aug 2020 02:10:34 +0800 Subject: [PATCH 46/56] [macos] add NSWindow.hasShadow support (#1637) * [macos] add NSWindow.hasShadow * change log * cargo fmt * Update CHANGELOG.md * Update src/platform_impl/macos/window.rs * Update src/platform/macos.rs * set_has_shadow() with cuter format * adjust code * cargo fmt * changelog --- CHANGELOG.md | 1 + src/platform/macos.rs | 23 +++++++++++++++++++ src/platform_impl/macos/window.rs | 38 ++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae81ae6f3b..c109c9c085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. +- On macOS, add `NSWindow.hasShadow` support. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f07e2f6e77..f6f18d448e 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -56,6 +56,12 @@ pub trait WindowExtMacOS { /// And allows the user to have a fullscreen window without using another /// space or taking control over the entire monitor. fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; + + /// Returns whether or not the window has shadow. + fn has_shadow(&self) -> bool; + + /// Sets whether or not the window has shadow. + fn set_has_shadow(&self, has_shadow: bool); } impl WindowExtMacOS for Window { @@ -83,6 +89,16 @@ impl WindowExtMacOS for Window { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.set_simple_fullscreen(fullscreen) } + + #[inline] + fn has_shadow(&self) -> bool { + self.window.has_shadow() + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + self.window.set_has_shadow(has_shadow) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -131,6 +147,7 @@ pub trait WindowBuilderExtMacOS { /// Build window with `resizeIncrements` property. Values must not be 0. fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; + fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; } impl WindowBuilderExtMacOS for WindowBuilder { @@ -190,6 +207,12 @@ impl WindowBuilderExtMacOS for WindowBuilder { self.platform_specific.disallow_hidpi = disallow_hidpi; self } + + #[inline] + fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder { + self.platform_specific.has_shadow = has_shadow; + self + } } /// Additional methods on `MonitorHandle` that are specific to MacOS. diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5505b661af..175cf73345 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -60,7 +60,7 @@ pub fn get_window_id(window_cocoa_id: id) -> Id { Id(window_cocoa_id as *const Object as _) } -#[derive(Clone, Default)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub activation_policy: ActivationPolicy, pub movable_by_window_background: bool, @@ -71,6 +71,25 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub fullsize_content_view: bool, pub resize_increments: Option>, pub disallow_hidpi: bool, + pub has_shadow: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + #[inline] + fn default() -> Self { + Self { + activation_policy: Default::default(), + movable_by_window_background: false, + titlebar_transparent: false, + title_hidden: false, + titlebar_hidden: false, + titlebar_buttons_hidden: false, + fullsize_content_view: false, + resize_increments: None, + disallow_hidpi: false, + has_shadow: true, + } + } } fn create_app(activation_policy: ActivationPolicy) -> Option { @@ -224,6 +243,10 @@ fn create_window( } } + if !pl_attrs.has_shadow { + ns_window.setHasShadow_(NO); + } + ns_window.center(); ns_window }); @@ -1094,6 +1117,19 @@ impl WindowExtMacOS for UnownedWindow { } } } + + #[inline] + fn has_shadow(&self) -> bool { + unsafe { self.ns_window.hasShadow() == YES } + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + unsafe { + self.ns_window + .setHasShadow_(if has_shadow { YES } else { NO }) + } + } } impl Drop for UnownedWindow { From 412bd94ea473c7b175f94a190d66f4be3e92aaab Mon Sep 17 00:00:00 2001 From: simlay Date: Fri, 14 Aug 2020 12:26:16 -0700 Subject: [PATCH 47/56] Renamed NSString to NSStringRust to support Debug View Heirarchy in Xcode (#1631) * Renamed NSString to NSStringRust to support Debug View Heirarchy * Updated from comments * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/platform_impl/ios/event_loop.rs | 5 +++-- src/platform_impl/ios/ffi.rs | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c109c9c085..c875fe4193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode. - On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 2e7c64d2a3..9536e94250 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -24,7 +24,8 @@ use crate::platform_impl::platform::{ CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, - CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom, + CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain, + UIUserInterfaceIdiom, }, monitor, view, MonitorHandle, }; @@ -117,7 +118,7 @@ impl EventLoop { 0, ptr::null(), nil, - NSString::alloc(nil).init_str("AppDelegate"), + NSStringRust::alloc(nil).init_str("AppDelegate"), ); unreachable!() } diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 1c464b066a..071b586795 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -359,7 +359,10 @@ pub struct CFRunLoopSourceContext { pub perform: Option, } -pub trait NSString: Sized { +// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of +// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy +// so please test if you change the name back to NSString. +pub trait NSStringRust: Sized { unsafe fn alloc(_: Self) -> id { msg_send![class!(NSString), alloc] } @@ -370,7 +373,7 @@ pub trait NSString: Sized { unsafe fn UTF8String(self) -> *const c_char; } -impl NSString for id { +impl NSStringRust for id { unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id { msg_send![self, initWithUTF8String: c_string as id] } From 9c72cc2a983401ad96f40555751bbfa1335259b6 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 17 Aug 2020 16:48:29 -0700 Subject: [PATCH 48/56] Fix HiDPI vs. set_cursor_icon for web (#1652) PhysicalSize is recorded as canvas.size, whereas LogicalSize is stored as canvas.style.size. The previous cursor behavior on stdweb clobbered all style - thus losing the LogicalSize. --- CHANGELOG.md | 1 + src/platform_impl/web/event_loop/mod.rs | 3 +-- src/platform_impl/web/stdweb/mod.rs | 8 ++++++-- src/platform_impl/web/web_sys/mod.rs | 12 +++++++----- src/platform_impl/web/window.rs | 3 +-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c875fe4193..e27cf5ffa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas +- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling - On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index fa01b5df05..bf9806bde2 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -6,11 +6,10 @@ mod window_target; pub use self::proxy::Proxy; pub use self::window_target::WindowTarget; -use super::{backend, device, monitor, window}; +use super::{backend, device, window}; use crate::event::Event; use crate::event_loop as root; -use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; use std::marker::PhantomData; pub struct EventLoop { diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 3632307ca7..e3dafb1be3 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -67,9 +67,13 @@ pub fn set_canvas_size(raw: &CanvasElement, size: Size) { raw.set_width(physical_size.width); raw.set_height(physical_size.height); + set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width)); + set_canvas_style_property(raw, "height", &format!("{}px", logical_size.height)); +} + +pub fn set_canvas_style_property(raw: &CanvasElement, style_attribute: &str, value: &str) { js! { - @{raw.as_ref()}.style.width = @{logical_size.width} + "px"; - @{raw.as_ref()}.style.height = @{logical_size.height} + "px"; + @{raw.as_ref()}.style[@{style_attribute}] = @{value}; } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 94efe1ec27..a4268b5272 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -81,13 +81,15 @@ pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) { raw.set_width(physical_size.width); raw.set_height(physical_size.height); + set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width)); + set_canvas_style_property(raw, "height", &format!("{}px", logical_size.height)); +} + +pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) { let style = raw.style(); style - .set_property("width", &format!("{}px", logical_size.width)) - .expect("Failed to set canvas width"); - style - .set_property("height", &format!("{}px", logical_size.height)) - .expect("Failed to set canvas height"); + .set_property(property, value) + .expect(&format!("Failed to set {}", property)); } pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 58d551354b..bcf701164e 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -165,8 +165,7 @@ impl Window { CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; - self.canvas - .set_attribute("style", &format!("cursor: {}", text)); + backend::set_canvas_style_property(self.canvas.raw(), "cursor", text); } #[inline] From 89d4c06decf95bfcc0c11d86bb8eb0f9f634cb36 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 20 Aug 2020 18:12:01 +0000 Subject: [PATCH 49/56] Fix crash on NetBSD The `_lwp_self` function cannot be used to reliably determine the main thread, see https://github.com/alacritty/alacritty/issues/2631#issuecomment-676723289. It might always be equal to the PID, but it's certainly not always 1 when the thread is the main thread. However, Rust's built in `Thread::id` and `Thread::name` function will always return `ThreadId(1)` and `Some("main")`. Since converting the thread's ID to a number is not supported on stable Rust, checking that the thread is labeled `Some("main")` seems like the most reliable option. It should also be a good fallback in general. --- CHANGELOG.md | 1 + src/platform_impl/linux/mod.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27cf5ffa9..2b3b767ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - On macOS, add `NSWindow.hasShadow` support. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. +- On NetBSD, fixed crash due to incorrect detection of the main thread. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 0fee68324e..73b8477760 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -730,7 +730,5 @@ fn is_main_thread() -> bool { #[cfg(target_os = "netbsd")] fn is_main_thread() -> bool { - use libc::_lwp_self; - - unsafe { _lwp_self() == 1 } + std::thread::current().name() == Some("main") } From 6ba583d19896172f5eca9d8551119906e4eff2b2 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Fri, 21 Aug 2020 09:09:04 +0800 Subject: [PATCH 50/56] Fix vertical scroll being inverted on web targets (#1665) --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/web_sys/event.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3b767ffc..2bb4519768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. +- On Web, fix vertical mouse wheel scrolling being inverted. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 8464c616bb..d57f9bb5bc 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -32,7 +32,7 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { let x = event.delta_x(); - let y = event.delta_y(); + let y = -event.delta_y(); match event.delta_mode() { MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index a06418410c..316b4f271d 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -31,7 +31,7 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { let x = event.delta_x(); - let y = event.delta_y(); + let y = -event.delta_y(); match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), From 0f7c82d38fbe52831a51d5b2523e838d1da02407 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sat, 22 Aug 2020 08:23:08 +0800 Subject: [PATCH 51/56] Send CursorMove before mouse press event and note that touch is unimplemented on web target (#1668) * Change to send CursorMove before mouse press event on web target * Fix feature matrix to indicate touch being unimplemented on web --- FEATURES.md | 6 +++--- src/platform_impl/web/event_loop/window_target.rs | 13 ++++++++++++- src/platform_impl/web/stdweb/canvas.rs | 3 ++- src/platform_impl/web/web_sys/canvas.rs | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index cdd8cc9f13..d7791f5b76 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -198,9 +198,9 @@ Legend: |Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| |Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 3ba5e1d2b5..d1851ea0cb 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -133,7 +133,18 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_press(move |pointer_id, button, modifiers| { + canvas.on_mouse_press(move |pointer_id, position, button, modifiers| { + // A mouse down event may come in without any prior CursorMoved events, + // therefore we should send a CursorMoved event to make sure that the + // user code has the correct cursor position. + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::CursorMoved { + device_id: DeviceId(device::Id(pointer_id)), + position, + modifiers, + }, + }); runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::MouseInput { diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 64f6a18f10..5122183710 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -200,11 +200,12 @@ impl Canvas { pub fn on_mouse_press(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 617f6ed61a..ae7e8091c2 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -248,7 +248,7 @@ impl Canvas { pub fn on_mouse_press(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { if has_pointer_event() { self.on_pointer_press = Some(self.add_user_event( @@ -256,6 +256,7 @@ impl Canvas { move |event: PointerEvent| { handler( event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); @@ -266,6 +267,7 @@ impl Canvas { Some(self.add_user_event("mousedown", move |event: MouseEvent| { handler( 0, + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); From bea60930b685641756d441fc83d06e4730d332b2 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Thu, 27 Aug 2020 00:11:27 +0800 Subject: [PATCH 52/56] Use send_events instead of send_event in web backend (#1681) --- .../web/event_loop/window_target.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d1851ea0cb..03fe2110ae 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -137,23 +137,25 @@ impl WindowTarget { // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::CursorMoved { - device_id: DeviceId(device::Id(pointer_id)), - position, - modifiers, - }, - }); - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::MouseInput { - device_id: DeviceId(device::Id(pointer_id)), - state: ElementState::Pressed, - button, - modifiers, - }, - }); + runner.send_events( + std::iter::once(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::CursorMoved { + device_id: DeviceId(device::Id(pointer_id)), + position, + modifiers, + }, + }) + .chain(std::iter::once(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::MouseInput { + device_id: DeviceId(device::Id(pointer_id)), + state: ElementState::Pressed, + button, + modifiers, + }, + })), + ); }); let runner = self.runner.clone(); From 02a34a167ab281d7cca9908f67928b659d428b39 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sat, 29 Aug 2020 21:34:33 +0800 Subject: [PATCH 53/56] Impl. mouse capturing on web target (#1672) * Impl. mouse capturing for web-sys with PointerEvent * Impl. mouse capturing for web-sys with MouseEvent by manual tracking * Reorganize web-sys backend mouse and pointer handling code * Impl. mouse capturing for stdweb with PointerEvent * Add mouse capturing for web target to changelog --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/platform_impl/web/stdweb/canvas.rs | 4 + src/platform_impl/web/web_sys/canvas.rs | 250 +++++++++--------- .../web/web_sys/canvas/mouse_handler.rs | 203 ++++++++++++++ .../web/web_sys/canvas/pointer_handler.rs | 103 ++++++++ src/platform_impl/web/web_sys/event.rs | 13 +- 7 files changed, 446 insertions(+), 129 deletions(-) create mode 100644 src/platform_impl/web/web_sys/canvas/mouse_handler.rs create mode 100644 src/platform_impl/web/web_sys/canvas/pointer_handler.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb4519768..b8077d4a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. +- On Web, implement mouse capturing for click-dragging out of the canvas. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/Cargo.toml b/Cargo.toml index 017e5927ab..9659f7cf72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ version = "0.3.22" optional = true features = [ 'console', + "AddEventListenerOptions", 'CssStyleDeclaration', 'BeforeUnloadEvent', 'Document', diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 5122183710..1fdc7182da 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -202,6 +202,7 @@ impl Canvas { where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { + let canvas = self.raw.clone(); self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), @@ -209,6 +210,9 @@ impl Canvas { event::mouse_button(&event), event::mouse_modifiers(&event), ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); })); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ae7e8091c2..81e152c1e9 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,36 +9,33 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, - PointerEvent, WheelEvent, + AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, + MediaQueryListEvent, MouseEvent, WheelEvent, }; +mod mouse_handler; +mod pointer_handler; + pub struct Canvas { - /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. - raw: HtmlCanvasElement, + common: Common, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, on_received_character: Option>, - on_cursor_leave: Option>, - on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, - // Fallback events when pointer event support is missing - on_mouse_leave: Option>, - on_mouse_enter: Option>, - on_mouse_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, - wants_fullscreen: Rc>, on_dark_mode: Option>, + mouse_state: MouseState, +} + +struct Common { + /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. + raw: HtmlCanvasElement, + wants_fullscreen: Rc>, } -impl Drop for Canvas { +impl Drop for Common { fn drop(&mut self) { self.raw.remove(); } @@ -72,38 +69,38 @@ impl Canvas { .set_attribute("tabindex", "0") .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + let mouse_state = if has_pointer_event() { + MouseState::HasPointerEvent(pointer_handler::PointerHandler::new()) + } else { + MouseState::NoPointerEvent(mouse_handler::MouseHandler::new()) + }; + Ok(Canvas { - raw: canvas, + common: Common { + raw: canvas, + wants_fullscreen: Rc::new(RefCell::new(false)), + }, on_blur: None, on_focus: None, on_keyboard_release: None, on_keyboard_press: None, on_received_character: None, - on_cursor_leave: None, - on_cursor_enter: None, - on_cursor_move: None, - on_pointer_release: None, - on_pointer_press: None, - on_mouse_leave: None, - on_mouse_enter: None, - on_mouse_move: None, - on_mouse_press: None, - on_mouse_release: None, on_mouse_wheel: None, on_fullscreen_change: None, - wants_fullscreen: Rc::new(RefCell::new(false)), on_dark_mode: None, + mouse_state, }) } pub fn set_attribute(&self, attribute: &str, value: &str) { - self.raw + self.common + .raw .set_attribute(attribute, value) .expect(&format!("Set attribute: {}", attribute)); } pub fn position(&self) -> LogicalPosition { - let bounds = self.raw.get_bounding_client_rect(); + let bounds = self.common.raw.get_bounding_client_rect(); LogicalPosition { x: bounds.x(), @@ -113,20 +110,20 @@ impl Canvas { pub fn size(&self) -> PhysicalSize { PhysicalSize { - width: self.raw.width(), - height: self.raw.height(), + width: self.common.raw.width(), + height: self.common.raw.height(), } } pub fn raw(&self) -> &HtmlCanvasElement { - &self.raw + &self.common.raw } pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), { - self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| { + self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { handler(); })); } @@ -135,7 +132,7 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| { + self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { handler(); })); } @@ -144,30 +141,34 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = - Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + self.on_keyboard_release = Some(self.common.add_user_event( + "keyup", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = - Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + self.on_keyboard_press = Some(self.common.add_user_event( + "keydown", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_received_character(&mut self, mut handler: F) @@ -179,7 +180,7 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = Some(self.add_user_event( + self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { handler(event::codepoint(&event)); @@ -187,115 +188,53 @@ impl Canvas { )); } - pub fn on_cursor_leave(&mut self, mut handler: F) + pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_leave = - Some(self.add_event("pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_leave(&self.common, handler), } } - pub fn on_cursor_enter(&mut self, mut handler: F) + pub fn on_cursor_enter(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_enter = - Some(self.add_event("pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_enter(&self.common, handler), } } - pub fn on_mouse_release(&mut self, mut handler: F) + pub fn on_mouse_release(&mut self, handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_release = Some(self.add_user_event( - "pointerup", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_release = - Some(self.add_user_event("mouseup", move |event: MouseEvent| { - handler( - 0, - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, handler), } } - pub fn on_mouse_press(&mut self, mut handler: F) + pub fn on_mouse_press(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_press = Some(self.add_user_event( - "pointerdown", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_press = - Some(self.add_user_event("mousedown", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_press(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, handler), } } - pub fn on_cursor_move(&mut self, mut handler: F) + pub fn on_cursor_move(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { - if has_pointer_event() { - self.on_cursor_move = - Some(self.add_event("pointermove", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); - } else { - self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, handler), } } @@ -303,7 +242,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { - self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { + self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); @@ -315,8 +254,10 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_fullscreen_change = - Some(self.add_event("fullscreenchange", move |_: Event| handler())); + self.on_fullscreen_change = Some( + self.common + .add_event("fullscreenchange", move |_: Event| handler()), + ); } pub fn on_dark_mode(&mut self, mut handler: F) @@ -341,6 +282,16 @@ impl Canvas { }); } + pub fn request_fullscreen(&self) { + self.common.request_fullscreen() + } + + pub fn is_fullscreen(&self) -> bool { + self.common.is_fullscreen() + } +} + +impl Common { fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, @@ -386,6 +337,44 @@ impl Canvas { }) } + // This function is used exclusively for mouse events (not pointer events). + // Due to the need for mouse capturing, the mouse event handlers are added + // to the window instead of the canvas element, which requires special + // handling to control event propagation. + fn add_window_mouse_event( + &self, + event_name: &str, + mut handler: F, + ) -> Closure + where + F: 'static + FnMut(MouseEvent), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + let window = web_sys::window().expect("Failed to obtain window"); + + let closure = Closure::wrap(Box::new(move |event: MouseEvent| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas + .request_fullscreen() + .expect("Failed to enter fullscreen"); + *wants_fullscreen.borrow_mut() = false; + } + }) as Box); + + window + .add_event_listener_with_callback_and_add_event_listener_options( + event_name, + &closure.as_ref().unchecked_ref(), + AddEventListenerOptions::new().capture(true), + ) + .expect("Failed to add event listener with callback and options"); + + closure + } + pub fn request_fullscreen(&self) { *self.wants_fullscreen.borrow_mut() = true; } @@ -395,6 +384,11 @@ impl Canvas { } } +enum MouseState { + HasPointerEvent(pointer_handler::PointerHandler), + NoPointerEvent(mouse_handler::MouseHandler), +} + /// Returns whether pointer events are supported. /// Used to decide whether to use pointer events /// or plain mouse events. Note that Safari diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs new file mode 100644 index 0000000000..37fdd018e3 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -0,0 +1,203 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::closure::Closure; +use web_sys::{EventTarget, MouseEvent}; + +pub(super) struct MouseHandler { + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, + on_mouse_leave_handler: Rc>>>, + mouse_capture_state: Rc>, +} + +#[derive(PartialEq, Eq)] +pub(super) enum MouseCaptureState { + NotCaptured, + Captured, + OtherElement, +} + +impl MouseHandler { + pub fn new() -> Self { + Self { + on_mouse_leave: None, + on_mouse_enter: None, + on_mouse_move: None, + on_mouse_press: None, + on_mouse_release: None, + on_mouse_leave_handler: Rc::new(RefCell::new(None)), + mouse_capture_state: Rc::new(RefCell::new(MouseCaptureState::NotCaptured)), + } + } + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, handler: F) + where + F: 'static + FnMut(i32), + { + *self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler)); + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_leave = Some(canvas_common.add_event("mouseout", move |_: MouseEvent| { + // If the mouse is being captured, it is always considered + // to be "within" the the canvas, until the capture has been + // released, therefore we don't send cursor leave events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + })); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| { + // We don't send cursor leave events when the mouse is being + // captured, therefore we do the same with cursor enter events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + handler(0); + } + })); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_release = Some(canvas_common.add_window_mouse_event( + "mouseup", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + // Shouldn't happen but we'll just ignore it. + MouseCaptureState::NotCaptured => return, + MouseCaptureState::OtherElement => { + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + return; + } + MouseCaptureState::Captured => {} + } + event.stop_propagation(); + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) + { + // Since we do not send cursor leave events while the + // cursor is being captured, we instead send it after + // the capture has been released. + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_press = Some(canvas_common.add_window_mouse_event( + "mousedown", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + MouseCaptureState::NotCaptured + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) => + { + // The target isn't our canvas which means the + // mouse is pressed outside of it. + *mouse_capture_state = MouseCaptureState::OtherElement; + return; + } + MouseCaptureState::OtherElement => return, + _ => {} + } + *mouse_capture_state = MouseCaptureState::Captured; + event.stop_propagation(); + handler( + 0, + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_move = Some(canvas_common.add_window_mouse_event( + "mousemove", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mouse_capture_state = mouse_capture_state.borrow(); + let is_over_canvas = event + .target() + .map_or(false, |target| target == EventTarget::from(canvas.clone())); + match &*mouse_capture_state { + // Don't handle hover events outside of canvas. + MouseCaptureState::NotCaptured if !is_over_canvas => return, + MouseCaptureState::OtherElement if !is_over_canvas => return, + // If hovering over the canvas, just send the cursor move event. + MouseCaptureState::NotCaptured + | MouseCaptureState::OtherElement + | MouseCaptureState::Captured => { + if *mouse_capture_state == MouseCaptureState::Captured { + event.stop_propagation(); + } + let mouse_pos = if is_over_canvas { + event::mouse_position(&event) + } else { + // Since the mouse is not on the canvas, we cannot + // use `offsetX`/`offsetY`. + event::mouse_position_by_client(&event, &canvas) + }; + handler( + 0, + mouse_pos.to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + } + } + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs new file mode 100644 index 0000000000..f9787dd5d5 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -0,0 +1,103 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use wasm_bindgen::closure::Closure; +use web_sys::PointerEvent; + +pub(super) struct PointerHandler { + on_cursor_leave: Option>, + on_cursor_enter: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, +} + +impl PointerHandler { + pub fn new() -> Self { + Self { + on_cursor_leave: None, + on_cursor_enter: None, + on_cursor_move: None, + on_pointer_press: None, + on_pointer_release: None, + } + } + + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_leave = Some(canvas_common.add_event( + "pointerout", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_enter = Some(canvas_common.add_event( + "pointerover", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_pointer_release = Some(canvas_common.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let canvas = canvas_common.raw.clone(); + self.on_pointer_press = Some(canvas_common.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + self.on_cursor_move = Some(canvas_common.add_event( + "pointermove", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 316b4f271d..18927d401f 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -2,7 +2,7 @@ use crate::dpi::LogicalPosition; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use std::convert::TryInto; -use web_sys::{KeyboardEvent, MouseEvent, WheelEvent}; +use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; pub fn mouse_button(event: &MouseEvent) -> MouseButton { match event.button() { @@ -29,6 +29,17 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { } } +pub fn mouse_position_by_client( + event: &MouseEvent, + canvas: &HtmlCanvasElement, +) -> LogicalPosition { + let bounding_client_rect = canvas.get_bounding_client_rect(); + LogicalPosition { + x: event.client_x() as f64 - bounding_client_rect.x(), + y: event.client_y() as f64 - bounding_client_rect.y(), + } +} + pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { let x = event.delta_x(); let y = -event.delta_y(); From a2db4c0a320aafc10d240c432fe5ef4e4d84629d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 29 Aug 2020 13:38:41 +0000 Subject: [PATCH 54/56] Unify Minus/Subtract virtual keycodes On all platforms other than Linux/X11, the Subtract key was uniformly used only for the Numpad. To make this cross-platform compatible, the `-` key will now map to `Minus` on X11 instead of `Subtract`. Since people have been confused about the difference between `Minus` and `Subtract` in the past, the `Subtract` key has also been renamed to `NumpadSubtract`. This is a breaking change that might be annoying to downstream since there's no direct improvement, but it should help new users in the future. Alternatively this could just be documented, rather than explicitly mentioning the Numpad in the name. --- CHANGELOG.md | 2 ++ src/event.rs | 8 ++++---- src/platform_impl/linux/wayland/keyboard.rs | 3 +-- src/platform_impl/linux/x11/events.rs | 4 ++-- src/platform_impl/macos/event.rs | 2 +- src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/web_sys/event.rs | 2 +- src/platform_impl/windows/event.rs | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8077d4a75..cc4386eaa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. +- **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract` +- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract` # 0.22.2 (2020-05-16) diff --git a/src/event.rs b/src/event.rs index 9e9320c2da..134421aca1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -892,6 +892,10 @@ pub enum VirtualKeyCode { Numpad7, Numpad8, Numpad9, + NumpadComma, + NumpadEnter, + NumpadEquals, + NumpadSubtract, AbntC1, AbntC2, @@ -930,9 +934,6 @@ pub enum VirtualKeyCode { NavigateBackward, NextTrack, NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, OEM102, Period, PlayPause, @@ -947,7 +948,6 @@ pub enum VirtualKeyCode { Slash, Sleep, Stop, - Subtract, Sysrq, Tab, Underline, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 8f911888f5..fb9219e0d3 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -340,7 +340,7 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract), + keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide), keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), @@ -360,7 +360,6 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), // => Some(VirtualKeyCode::Sleep), // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Subtract), // => Some(VirtualKeyCode::Sysrq), keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index 1b4996e745..f7d98b98da 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -84,7 +84,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { //ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, ffi::XK_KP_Add => VirtualKeyCode::Add, //ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator, - ffi::XK_KP_Subtract => VirtualKeyCode::Subtract, + ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract, //ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal, ffi::XK_KP_Divide => VirtualKeyCode::Divide, ffi::XK_KP_0 => VirtualKeyCode::Numpad0, @@ -186,7 +186,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { //ffi::XK_asterisk => VirtualKeyCode::Asterisk, ffi::XK_plus => VirtualKeyCode::Add, ffi::XK_comma => VirtualKeyCode::Comma, - ffi::XK_minus => VirtualKeyCode::Subtract, + ffi::XK_minus => VirtualKeyCode::Minus, ffi::XK_period => VirtualKeyCode::Period, ffi::XK_slash => VirtualKeyCode::Slash, ffi::XK_0 => VirtualKeyCode::Key0, diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 6e231940f2..dd12c9737e 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -169,7 +169,7 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option { 0x4b => VirtualKeyCode::Divide, 0x4c => VirtualKeyCode::NumpadEnter, //0x4d => unkown, - 0x4e => VirtualKeyCode::Subtract, + 0x4e => VirtualKeyCode::NumpadSubtract, 0x4f => VirtualKeyCode::F18, 0x50 => VirtualKeyCode::F19, 0x51 => VirtualKeyCode::NumpadEquals, diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index d57f9bb5bc..e9bb1b38c5 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -195,7 +195,7 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { "Slash" => VirtualKeyCode::Slash, "Sleep" => VirtualKeyCode::Sleep, "Stop" => VirtualKeyCode::Stop, - "NumpadSubtract" => VirtualKeyCode::Subtract, + "NumpadSubtract" => VirtualKeyCode::NumpadSubtract, "Sysrq" => VirtualKeyCode::Sysrq, "Tab" => VirtualKeyCode::Tab, "Underline" => VirtualKeyCode::Underline, diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 18927d401f..802915683b 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -204,7 +204,7 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { "Slash" => VirtualKeyCode::Slash, "Sleep" => VirtualKeyCode::Sleep, "Stop" => VirtualKeyCode::Stop, - "NumpadSubtract" => VirtualKeyCode::Subtract, + "NumpadSubtract" => VirtualKeyCode::NumpadSubtract, "Sysrq" => VirtualKeyCode::Sysrq, "Tab" => VirtualKeyCode::Tab, "Underline" => VirtualKeyCode::Underline, diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 49501f1a3c..40312418bc 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -262,7 +262,7 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { winuser::VK_MULTIPLY => Some(VirtualKeyCode::Multiply), winuser::VK_ADD => Some(VirtualKeyCode::Add), //winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator), - winuser::VK_SUBTRACT => Some(VirtualKeyCode::Subtract), + winuser::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), winuser::VK_DECIMAL => Some(VirtualKeyCode::Decimal), winuser::VK_DIVIDE => Some(VirtualKeyCode::Divide), winuser::VK_F1 => Some(VirtualKeyCode::F1), From 658a9a4ea8b50c5279042aa2a23f27fc583bb878 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sun, 30 Aug 2020 21:15:44 +0800 Subject: [PATCH 55/56] Handle scale factor change on web-sys backend (#1690) * Change web backend `event_handler` to without 'static lifetime * Refactor web runner and fix ControlFlow::Exit not being sticky * Impl. scaling change event for web-sys backend * Improve `dpi` docs regarding the web backend * Add changes to changelog * Update features.md --- CHANGELOG.md | 2 + FEATURES.md | 4 +- src/dpi.rs | 3 + src/event_loop.rs | 7 +- src/platform_impl/web/event_loop/mod.rs | 3 +- src/platform_impl/web/event_loop/runner.rs | 185 ++++++++++++++---- .../web/event_loop/window_target.rs | 7 +- src/platform_impl/web/mod.rs | 6 + src/platform_impl/web/stdweb/mod.rs | 2 + src/platform_impl/web/stdweb/scaling.rs | 13 ++ src/platform_impl/web/web_sys/mod.rs | 2 + src/platform_impl/web/web_sys/scaling.rs | 110 +++++++++++ 12 files changed, 301 insertions(+), 43 deletions(-) create mode 100644 src/platform_impl/web/stdweb/scaling.rs create mode 100644 src/platform_impl/web/web_sys/scaling.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cc4386eaa8..2eb312ca80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. - On Web, implement mouse capturing for click-dragging out of the canvas. +- On Web, fix `ControlFlow::Exit` not properly handled. +- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/FEATURES.md b/FEATURES.md index d7791f5b76..ca28c417f1 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -182,9 +182,11 @@ Legend: |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| -|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**| +|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1| |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +\*1: `WindowEvent::ScaleFactorChanged` is not sent on `stdweb` backend. + ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | diff --git a/src/dpi.rs b/src/dpi.rs index 8a56ae6e06..ed30abb79b 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -89,6 +89,8 @@ //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. +//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by +//! both the screen scaling and the browser zoom level and can go below `1.0`. //! //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) @@ -96,6 +98,7 @@ //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ //! [android_1]: https://developer.android.com/training/multiscreen/screendensities +//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; diff --git a/src/event_loop.rs b/src/event_loop.rs index a523c01aed..02402930d7 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -72,8 +72,11 @@ impl fmt::Debug for EventLoopWindowTarget { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. For web, events are sent when - /// `requestAnimationFrame` fires. + /// whether or not new events are available to process. + /// + /// For web, events are queued and usually sent when `requestAnimationFrame` fires but sometimes + /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for + /// example when the scaling of the page has changed. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index bf9806bde2..24be4d3dcc 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -28,8 +28,7 @@ impl EventLoop { pub fn run(self, mut event_handler: F) -> ! where - F: 'static - + FnMut(Event<'static, T>, &root::EventLoopWindowTarget, &mut root::ControlFlow), + F: 'static + FnMut(Event<'_, T>, &root::EventLoopWindowTarget, &mut root::ControlFlow), { let target = root::EventLoopWindowTarget { p: self.elw.p.clone(), diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index d381b942b2..0ccd7862c2 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,4 +1,4 @@ -use super::{backend, state::State}; +use super::{super::ScaleChangeArgs, backend, state::State}; use crate::event::{Event, StartCause}; use crate::event_loop as root; use crate::window::WindowId; @@ -24,23 +24,60 @@ pub struct Execution { runner: RefCell>>, events: RefCell>>, id: RefCell, + all_canvases: RefCell>, redraw_pending: RefCell>, + scale_change_detector: RefCell>, } struct Runner { state: State, is_busy: bool, - event_handler: Box, &mut root::ControlFlow)>, + event_handler: Box, &mut root::ControlFlow)>, } impl Runner { - pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { + pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { Runner { state: State::Init, is_busy: false, event_handler, } } + + /// Returns the cooresponding `StartCause` for the current `state`, or `None` + /// when in `Exit` state. + fn maybe_start_cause(&self) -> Option { + Some(match self.state { + State::Init => StartCause::Init, + State::Poll { .. } => StartCause::Poll, + State::Wait { start } => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { + start, + requested_resume: Some(end), + }, + State::Exit => return None, + }) + } + + fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) { + let is_closed = *control == root::ControlFlow::Exit; + + // An event is being processed, so the runner should be marked busy + self.is_busy = true; + + (self.event_handler)(event, control); + + // Maintain closed state, even if the callback changes it + if is_closed { + *control = root::ControlFlow::Exit; + } + + // An event is no longer being processed + self.is_busy = false; + } } impl Shared { @@ -49,16 +86,22 @@ impl Shared { runner: RefCell::new(None), events: RefCell::new(VecDeque::new()), id: RefCell::new(0), + all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), + scale_change_detector: RefCell::new(None), })) } + pub fn add_canvas(&self, id: WindowId, canvas: backend::RawCanvasType) { + self.0.all_canvases.borrow_mut().push((id, canvas)); + } + // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootEventLoopWindowTarget reference pub fn set_listener( &self, - event_handler: Box, &mut root::ControlFlow)>, + event_handler: Box, &mut root::ControlFlow)>, ) { self.0.runner.replace(Some(Runner::new(event_handler))); self.init(); @@ -67,6 +110,14 @@ impl Shared { backend::on_unload(move || close_instance.handle_unload()); } + pub(crate) fn set_on_scale_change(&self, handler: F) + where + F: 'static + FnMut(ScaleChangeArgs), + { + *self.0.scale_change_detector.borrow_mut() = + Some(backend::ScaleChangeDetector::new(handler)); + } + // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { @@ -138,25 +189,15 @@ impl Shared { } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events - let start_cause = if let Some(runner) = &*self.0.runner.borrow() { - match runner.state { - State::Init => StartCause::Init, - State::Poll { .. } => StartCause::Poll, - State::Wait { start } => StartCause::WaitCancelled { - start, - requested_resume: None, - }, - State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { - start, - requested_resume: Some(end), - }, - State::Exit => { - // If we're in the exit state, don't do event processing - return; - } - } - } else { - unreachable!("The runner cannot process events when it is not attached"); + let start_cause = match (self.0.runner.borrow().as_ref()) + .unwrap_or_else(|| { + unreachable!("The runner cannot process events when it is not attached") + }) + .maybe_start_cause() + { + Some(c) => c, + // If we're in the exit state, don't do event processing + None => return, }; // Take the start event, then the events provided to this function, and run an iteration of // the event loop @@ -191,37 +232,107 @@ impl Shared { } } + pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) { + let start_cause = match (self.0.runner.borrow().as_ref()) + .unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner")) + .maybe_start_cause() + { + Some(c) => c, + // If we're in the exit state, don't do event processing + None => return, + }; + let mut control = self.current_control_flow(); + + // Handle the start event and all other events in the queue. + self.handle_event(Event::NewEvents(start_cause), &mut control); + + // Now handle the `ScaleFactorChanged` events. + for &(id, ref canvas) in &*self.0.all_canvases.borrow() { + // First, we send the `ScaleFactorChanged` event: + let current_size = crate::dpi::PhysicalSize { + width: canvas.width() as u32, + height: canvas.height() as u32, + }; + let logical_size = current_size.to_logical::(old_scale); + let mut new_size = logical_size.to_physical(new_scale); + self.handle_single_event_sync( + Event::WindowEvent { + window_id: id, + event: crate::event::WindowEvent::ScaleFactorChanged { + scale_factor: new_scale, + new_inner_size: &mut new_size, + }, + }, + &mut control, + ); + + // Then we resize the canvas to the new size and send a `Resized` event: + backend::set_canvas_size(canvas, crate::dpi::Size::Physical(new_size)); + self.handle_single_event_sync( + Event::WindowEvent { + window_id: id, + event: crate::event::WindowEvent::Resized(new_size), + }, + &mut control, + ); + } + + self.handle_event(Event::MainEventsCleared, &mut control); + + // Discard all the pending redraw as we shall just redraw all windows. + self.0.redraw_pending.borrow_mut().clear(); + for &(window_id, _) in &*self.0.all_canvases.borrow() { + self.handle_event(Event::RedrawRequested(window_id), &mut control); + } + self.handle_event(Event::RedrawEventsCleared, &mut control); + + self.apply_control_flow(control); + // If the event loop is closed, it has been closed this iteration and now the closing + // event should be emitted + if self.is_closed() { + self.handle_event(Event::LoopDestroyed, &mut control); + } + } + fn handle_unload(&self) { self.apply_control_flow(root::ControlFlow::Exit); let mut control = self.current_control_flow(); self.handle_event(Event::LoopDestroyed, &mut control); } + // handle_single_event_sync takes in an event and handles it synchronously. + // + // It should only ever be called from `scale_changed`. + fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut root::ControlFlow) { + if self.is_closed() { + *control = root::ControlFlow::Exit; + } + match *self.0.runner.borrow_mut() { + Some(ref mut runner) => { + runner.handle_single_event(event, control); + } + _ => panic!("Cannot handle event synchronously without a runner"), + } + } + // handle_event takes in events and either queues them or applies a callback // - // It should only ever be called from send_event + // It should only ever be called from `run_until_cleared` and `scale_changed`. fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) { - let is_closed = self.is_closed(); + if self.is_closed() { + *control = root::ControlFlow::Exit; + } match *self.0.runner.borrow_mut() { Some(ref mut runner) => { - // An event is being processed, so the runner should be marked busy - runner.is_busy = true; - - (runner.event_handler)(event, control); - - // Maintain closed state, even if the callback changes it - if is_closed { - *control = root::ControlFlow::Exit; - } - - // An event is no longer being processed - runner.is_busy = false; + runner.handle_single_event(event, control); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed _ => self.0.events.borrow_mut().push_back(event), } + let is_closed = *control == root::ControlFlow::Exit; + // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().is_some() { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 03fe2110ae..0815bd40b7 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -29,8 +29,12 @@ impl WindowTarget { Proxy::new(self.runner.clone()) } - pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { + pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { self.runner.set_listener(event_handler); + let runner = self.runner.clone(); + self.runner.set_on_scale_change(move |arg| { + runner.handle_scale_changed(arg.old_scale, arg.new_scale) + }); } pub fn generate_id(&self) -> window::Id { @@ -40,6 +44,7 @@ impl WindowTarget { pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); + runner.add_canvas(WindowId(id), canvas.raw().clone()); canvas.on_blur(move || { runner.send_event(Event::WindowEvent { diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 43bbb2afff..ed170b83b2 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -46,3 +46,9 @@ pub use self::window::{ }; pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Copy)] +pub(crate) struct ScaleChangeArgs { + old_scale: f64, + new_scale: f64, +} diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index e3dafb1be3..2f38c178a8 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -1,8 +1,10 @@ mod canvas; mod event; +mod scaling; mod timeout; pub use self::canvas::Canvas; +pub use self::scaling::ScaleChangeDetector; pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; diff --git a/src/platform_impl/web/stdweb/scaling.rs b/src/platform_impl/web/stdweb/scaling.rs new file mode 100644 index 0000000000..28024735f1 --- /dev/null +++ b/src/platform_impl/web/stdweb/scaling.rs @@ -0,0 +1,13 @@ +use super::super::ScaleChangeArgs; + +pub struct ScaleChangeDetector(()); + +impl ScaleChangeDetector { + pub(crate) fn new(_handler: F) -> Self + where + F: 'static + FnMut(ScaleChangeArgs), + { + // TODO: Stub, unimplemented (see web_sys for reference). + Self(()) + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index a4268b5272..338869acd3 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,8 +1,10 @@ mod canvas; mod event; +mod scaling; mod timeout; pub use self::canvas::Canvas; +pub use self::scaling::ScaleChangeDetector; pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs new file mode 100644 index 0000000000..5018257fa5 --- /dev/null +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -0,0 +1,110 @@ +use super::super::ScaleChangeArgs; + +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::{prelude::Closure, JsCast}; +use web_sys::{MediaQueryList, MediaQueryListEvent}; + +pub struct ScaleChangeDetector(Rc>); + +impl ScaleChangeDetector { + pub(crate) fn new(handler: F) -> Self + where + F: 'static + FnMut(ScaleChangeArgs), + { + Self(ScaleChangeDetectorInternal::new(handler)) + } +} + +/// This is a helper type to help manage the `MediaQueryList` used for detecting +/// changes of the `devicePixelRatio`. +struct ScaleChangeDetectorInternal { + callback: Box, + closure: Option>, + mql: Option, + last_scale: f64, +} + +impl ScaleChangeDetectorInternal { + fn new(handler: F) -> Rc> + where + F: 'static + FnMut(ScaleChangeArgs), + { + let current_scale = super::scale_factor(); + let new_self = Rc::new(RefCell::new(Self { + callback: Box::new(handler), + closure: None, + mql: None, + last_scale: current_scale, + })); + + let cloned_self = new_self.clone(); + let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { + cloned_self.borrow_mut().handler(event) + }) as Box); + + let mql = Self::create_mql(&closure); + { + let mut borrowed_self = new_self.borrow_mut(); + borrowed_self.closure = Some(closure); + borrowed_self.mql = mql; + } + new_self + } + + fn create_mql(closure: &Closure) -> Option { + let window = web_sys::window().expect("Failed to obtain window"); + let current_scale = super::scale_factor(); + // This media query initially matches the current `devicePixelRatio`. + // We add 0.0001 to the lower and upper bounds such that it won't fail + // due to floating point precision limitations. + let media_query = format!( + "(min-resolution: {:.4}dppx) and (max-resolution: {:.4}dppx)", + current_scale - 0.0001, + current_scale + 0.0001, + ); + window + .match_media(&media_query) + .ok() + .flatten() + .and_then(|mql| { + assert_eq!(mql.matches(), true); + mql.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) + .map(|_| mql) + .ok() + }) + } + + fn handler(&mut self, event: MediaQueryListEvent) { + assert_eq!(event.matches(), false); + let closure = self + .closure + .as_ref() + .expect("DevicePixelRatioChangeDetector::closure should not be None"); + let mql = self + .mql + .take() + .expect("DevicePixelRatioChangeDetector::mql should not be None"); + mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) + .expect("Failed to remove listener from MediaQueryList"); + let new_scale = super::scale_factor(); + (self.callback)(ScaleChangeArgs { + old_scale: self.last_scale, + new_scale, + }); + let new_mql = Self::create_mql(closure); + self.mql = new_mql; + self.last_scale = new_scale; + } +} + +impl Drop for ScaleChangeDetectorInternal { + fn drop(&mut self) { + match (self.closure.as_ref(), self.mql.as_ref()) { + (Some(closure), Some(mql)) => { + let _ = + mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())); + } + _ => {} + } + } +} From c142719963a6e54b190e6c11e2470dfacc9fbe95 Mon Sep 17 00:00:00 2001 From: Jack Frost Date: Fri, 4 Sep 2020 23:08:32 +0000 Subject: [PATCH 56/56] housekeeping --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4a45022c36..e0f3c2994e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,7 @@ //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle #![deny(rust_2018_idioms)] -#![deny(intra_doc_link_resolution_failure)] +#![deny(broken_intra_doc_links)] #[allow(unused_imports)] #[macro_use]