Compare commits

..

2 Commits

Author SHA1 Message Date
Mads Marquart
89687656d6 Emit suspended on shutdown 2024-05-29 14:28:36 +02:00
Mads Marquart
9027d8f350 Verify event order
When debug assertions are enabled, check that events are emitted in the
expected order, and if not, log an error.
2024-05-29 14:28:35 +02:00
87 changed files with 1744 additions and 1818 deletions

17
.github/CODEOWNERS vendored
View File

@@ -1,11 +1,10 @@
# Android
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
/src/platform/android.rs @msiglreith @MarijnS95
/src/platform_impl/android @msiglreith @MarijnS95
# Apple (AppKit + UIKit)
# iOS
/src/platform/ios.rs @madsmtm
/src/platform/macos.rs @madsmtm
/src/platform_impl/apple @madsmtm
/src/platform_impl/ios @madsmtm
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
@@ -18,13 +17,17 @@
/src/platform/x11.rs @kchibisov @notgull
/src/platform_impl/linux/x11 @kchibisov @notgull
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# Web
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda
# Windows
/src/platform/windows.rs @notgull
/src/platform_impl/windows @notgull
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51

View File

@@ -10,7 +10,7 @@ jobs:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- uses: taiki-e/install-action@v2
with:
tool: typos-cli
@@ -42,7 +42,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.73']
toolchain: [stable, nightly, '1.70.0']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -61,10 +61,10 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.73'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.73'
- toolchain: '1.70.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
@@ -88,7 +88,7 @@ jobs:
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
@@ -149,13 +149,13 @@ jobs:
- name: Test dpi crate
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo test -p dpi
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
@@ -164,7 +164,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
@@ -174,7 +174,7 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
@@ -183,7 +183,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.73'
matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
@@ -220,24 +220,9 @@ jobs:
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
swc:
name: Minimize JavaScript
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1
- name: Install SWC
run: sudo npm i -g @swc/cli
- name: Run SWC
run: |
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
- name: Check for diff
run: |
[[ -z $(git status -s) ]]

3
.gitignore vendored
View File

@@ -3,5 +3,8 @@ target/
rls/
.vscode/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

12
.swcrc
View File

@@ -1,12 +0,0 @@
{
"minify": true,
"jsc": {
"target": "es2022",
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.3"
version = "0.30.0"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
@@ -86,11 +86,11 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
], optional = true }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
tracing = { version = "0.1.40", default-features = false }
tracing = { version = "0.1.40", default_features = false }
[dev-dependencies]
image = { version = "0.25.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
winit = { path = ".", features = ["rwh_05"] }
@@ -102,22 +102,20 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
"wayland-dlopen",
] }
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
objc2-foundation = { version = "0.2.2", features = [
"block2",
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"dispatch",
"NSArray",
"NSAttributedString",
@@ -125,7 +123,6 @@ objc2-foundation = { version = "0.2.2", features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
@@ -134,13 +131,15 @@ objc2-foundation = { version = "0.2.2", features = [
"NSString",
"NSThread",
"NSValue",
] }
objc2-app-kit = { version = "0.2.2", features = [
]
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
version = "0.2.2"
features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
@@ -162,11 +161,11 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
]
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"dispatch",
"NSArray",
"NSEnumerator",
@@ -176,8 +175,11 @@ objc2-foundation = { version = "0.2.2", features = [
"NSProcessInfo",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.2.2", features = [
]
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
version = "0.2.2"
features = [
"UIApplication",
"UIDevice",
"UIEvent",
@@ -196,12 +198,14 @@ objc2-ui-kit = { version = "0.2.2", features = [
"UIView",
"UIViewController",
"UIWindow",
] }
]
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = { version = "0.52.0", features = [
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.52.0"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
@@ -226,10 +230,9 @@ windows-sys = { version = "0.52.0", features = [
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
] }
]
# Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
@@ -245,8 +248,8 @@ rustix = { version = "0.38.4", default-features = false, features = [
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.9.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.0", default-features = false, features = [
sctk-adwaita = { version = "0.9.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.1", optional = true }
@@ -267,18 +270,17 @@ x11rb = { version = "0.13.0", default-features = false, features = [
], optional = true }
xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
web_sys = { package = "web-sys", version = "0.3.64", features = [
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'BlobPropertyBag',
'console',
'CssStyleDeclaration',
'Document',
@@ -314,9 +316,10 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
] }
]
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2"
@@ -340,7 +343,7 @@ resolver = "2"
members = ["dpi"]
[workspace.package]
rust-version = "1.73"
rust-version = "1.70.0"
repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0"
edition = "2021"

View File

@@ -154,6 +154,7 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.3"
winit = "0.30.0"
```
## [Documentation](https://docs.rs/winit)
@@ -35,7 +35,7 @@ another library.
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following

View File

@@ -10,9 +10,10 @@ fn main() {
android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" },
ios_platform: { all(target_vendor = "apple", not(target_os = "macos")) },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
free_unix: { all(unix, not(target_vendor = "apple"), not(android_platform), not(target_os = "emscripten")) },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.

View File

@@ -12,6 +12,4 @@ disallowed-methods = [
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "core_foundation::runloop::kCFRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" },
{ path = "objc2_foundation::NSRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" }
]

View File

@@ -85,7 +85,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.create_window(window_attributes).unwrap()
}
let event_loop = EventLoop::new().unwrap();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut app = Application::default();
event_loop.run_app(&mut app)
}

View File

@@ -17,7 +17,9 @@ use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event::{
DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, StartCause, WindowEvent,
};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
@@ -44,7 +46,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread.
@@ -54,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// from a different thread.
info!("Starting to send user event every second");
loop {
_event_loop_proxy.wake_up();
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
@@ -64,6 +66,12 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.run_app(&mut state).map_err(Into::into)
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
/// Application state and event handling.
struct Application {
/// Custom cursors assets.
@@ -79,7 +87,7 @@ struct Application {
}
impl Application {
fn new(event_loop: &EventLoop) -> Self {
fn new<T>(event_loop: &EventLoop<T>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
let context = Some(
@@ -206,12 +214,6 @@ impl Application {
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::SetTheme(theme) => {
window.window.set_theme(theme);
// Get the resulting current theme to draw with
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
window.set_draw_theme(actual_theme);
},
#[cfg(macos_platform)]
Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier();
@@ -302,9 +304,15 @@ impl Application {
}
}
impl ApplicationHandler for Application {
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
info!("User wake up");
impl ApplicationHandler<UserEvent> for Application {
fn new_events(&mut self, _event_loop: &ActiveEventLoop, start_cause: StartCause) {
if let StartCause::Init = start_cause {
info!("Started the event loop");
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
info!("User event: {event:?}");
}
fn window_event(
@@ -320,6 +328,7 @@ impl ApplicationHandler for Application {
match event {
WindowEvent::Resized(size) => {
info!("Resized({size:?})");
window.resize(size);
},
WindowEvent::Focused(focused) => {
@@ -334,7 +343,7 @@ impl ApplicationHandler for Application {
},
WindowEvent::ThemeChanged(theme) => {
info!("Theme changed to {theme:?}");
window.set_draw_theme(theme);
window.set_theme(theme);
},
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
@@ -733,8 +742,8 @@ impl WindowState {
self.window.request_redraw();
}
/// Change the theme that things are drawn in.
fn set_draw_theme(&mut self, theme: Theme) {
/// Change the theme.
fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
self.window.request_redraw();
}
@@ -884,7 +893,6 @@ enum Action {
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
@@ -916,9 +924,6 @@ impl Action {
Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => "Cycle option as alt mode",
Action::SetTheme(None) => "Change to the system theme",
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
#[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
@@ -1063,10 +1068,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Action::AnimationCustomCursor,
),
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
// K.
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)]
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)]

View File

@@ -5,7 +5,7 @@ use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
/// The handler of the application events.
pub trait ApplicationHandler {
pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This is a useful place to put code that should be done before you start processing
@@ -82,95 +82,11 @@ pub trait ApplicationHandler {
/// [`Suspended`]: Self::suspended
fn resumed(&mut self, event_loop: &ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
/// Emitted when an event is sent from [`EventLoopProxy::send_event`].
///
/// Multiple calls to the aforementioned method will be merged, and will only wake the event
/// loop once; however, due to the nature of multi-threading some wake ups may appear
/// spuriously. For these reasons, you should not rely on the number of times that this was
/// called.
///
/// The order in which this is emitted in relation to other events is not guaranteed. The time
/// at which this will be emitted is not guaranteed, only that it will happen "soon". That is,
/// there may be several executions of the event loop, including multiple redraws to windows,
/// between [`EventLoopProxy::wake_up()`] being called and the event being delivered.
///
/// [`EventLoopProxy::wake_up()`]: crate::event_loop::EventLoopProxy::wake_up
///
/// # Example
///
/// Use a [`std::sync::mpsc`] channel to handle events from a different thread.
///
/// ```no_run
/// use std::sync::mpsc;
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
/// }
///
/// impl ApplicationHandler for MyApp {
/// # fn window_event(
/// # &mut self,
/// # _event_loop: &ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
/// // Note: We take care not to use `recv` or `iter` here, as those are blocking,
/// // and that would be bad for performance and might lead to a deadlock.
/// for i in self.receiver.try_iter() {
/// println!("received: {i}");
/// }
/// }
///
/// // Rest of `ApplicationHandler`
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = EventLoop::new()?;
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
/// let mut i = 0;
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once `MyApp` is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
/// // Otherwise, `proxy_wake_up` might be triggered prematurely.
/// proxy.wake_up();
/// i += 1;
/// thread::sleep(Duration::from_secs(1));
/// }
/// });
///
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
/// }
/// ```
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
/// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
let _ = (event_loop, event);
}
/// Emitted when the OS sends an event to a winit window.
@@ -308,7 +224,7 @@ pub trait ApplicationHandler {
}
}
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -320,8 +236,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]
@@ -365,7 +281,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
}
}
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -377,8 +293,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]

View File

@@ -12,6 +12,7 @@ on how to add them:
### Added
- Add `Window::turbo()`, implemented on X11, Wayland, and Web.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
- On X11, add `Window::some_rare_api`.
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
@@ -40,26 +41,18 @@ changelog entry.
## Unreleased
### Changed
### Added
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.73`.
- Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
generic user event.
Winit will now only indicate that wake up happened, you will have to pair
this with an external mechanism like `std::sync::mpsc::channel` if you want
to send specific data to be processed on the main thread.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop.
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
- Remove `Event`.
- On iOS, remove `platform::ios::EventLoopExtIOS` and related `platform::ios::Idiom` type.
This feature was incomplete, and the equivalent functionality can be trivially achieved outside
of `winit` using `objc2-ui-kit` and calling `UIDevice::currentDevice().userInterfaceIdiom()`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.

View File

@@ -1,50 +1,3 @@
## 0.30.3
### Added
- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting
control flow strategies before starting the event loop.
- On Web, add `WaitUntilStrategy`, which allows to set different strategies for
`ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is
used, with a fallback to `setTimeout()` with a trick to circumvent throttling
to 4ms. But an option to use a Web worker to schedule the timer is available
as well, which commonly prevents any throttling when the window is not focused.
### Changed
- On macOS, set the window theme on the `NSWindow` instead of application-wide.
### Fixed
- On X11, build on arm platforms.
- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window.
## 0.30.2
### Fixed
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
when not called from inside the event loop. Now queues a microtask instead.
- On Web, stop overwriting default cursor with `CursorIcon::Default`.
- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`.
- On macOS, fix not working opacity for entire window.
## 0.30.1
### Added
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.
- On macOS, fix the range in `Ime::Preedit`.
- On macOS, use the system's internal mechanisms for queuing events.
- On macOS, handle events directly instead of queuing when possible.
## 0.30.0
### Added
@@ -148,7 +101,7 @@
we move particular `match event` arms into methods on `ApplicationHandler`,
for example:
```rust,no_run,ignore
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};
@@ -211,7 +164,7 @@
Using the migration example from above, you can change your code as follows:
```rust,no_run,ignore
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};

View File

@@ -1,4 +1,4 @@
//! The event enums and assorted supporting types.
//! The [`Event`] enum and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
@@ -54,15 +54,11 @@ use crate::platform_impl;
use crate::window::Window;
use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
pub enum Event<T: 'static> {
/// See [`ApplicationHandler::new_events`] for details.
///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
@@ -71,15 +67,18 @@ pub(crate) enum Event {
/// See [`ApplicationHandler::window_event`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: DeviceId, event: DeviceEvent },
/// See [`ApplicationHandler::user_event`] for details.
///
/// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event
UserEvent(T),
/// See [`ApplicationHandler::suspended`] for details.
///
/// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
@@ -104,9 +103,24 @@ pub(crate) enum Event {
///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
MemoryWarning,
}
/// User requested a wake up.
UserWakeUp,
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
}
/// Describes the reason the event loop is resuming.
@@ -375,8 +389,6 @@ pub enum WindowEvent {
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
///
/// ## Platform-specific
///
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
@@ -1018,6 +1030,7 @@ mod tests {
// Mainline events.
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
@@ -1101,12 +1114,25 @@ mod tests {
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event| {
foreach_event!(|event: event::Event<()>| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
@@ -1127,7 +1153,7 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event| {
foreach_event!(|event: event::Event<()>| {
let _ = format!("{:?}", event);
});
let _ = event::StartCause::Init.clone();

View File

@@ -3,16 +3,15 @@
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//! [`send_event`][EventLoopProxy::send_event] method.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt};
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
@@ -40,8 +39,8 @@ use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop,
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
@@ -59,15 +58,16 @@ pub struct ActiveEventLoop {
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::builder`].
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
#[derive(Default)]
pub struct EventLoopBuilder {
pub struct EventLoopBuilder<T: 'static> {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder {
impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
#[deprecated = "use `EventLoop::builder` instead"]
@@ -76,7 +76,7 @@ impl EventLoopBuilder {
}
}
impl EventLoopBuilder {
impl<T> EventLoopBuilder<T> {
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -111,7 +111,7 @@ impl EventLoopBuilder {
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
@@ -132,7 +132,7 @@ impl EventLoopBuilder {
}
}
impl fmt::Debug for EventLoop {
impl<T> fmt::Debug for EventLoop<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoop { .. }")
}
@@ -146,12 +146,12 @@ impl fmt::Debug for ActiveEventLoop {
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -189,12 +189,12 @@ impl ControlFlow {
}
}
impl EventLoop {
impl EventLoop<()> {
/// Create the event loop.
///
/// This is an alias of `EventLoop::builder().build()`.
#[inline]
pub fn new() -> Result<EventLoop, EventLoopError> {
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
Self::builder().build()
}
@@ -204,12 +204,122 @@ impl EventLoop {
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder {
EventLoopBuilder { platform_specific: Default::default() }
pub fn builder() -> EventLoopBuilder<()> {
Self::with_user_event()
}
}
impl EventLoop {
#[cfg(debug_assertions)]
pub(crate) fn ensure_event_order<'a, T: 'static>(
handler: impl ApplicationHandler<T> + 'a,
) -> impl ApplicationHandler<T> + 'a {
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::window::WindowId;
#[derive(Default, Debug, PartialEq, Eq, Clone)]
enum State {
#[default]
NotRunning,
Suspended,
Running,
Waiting,
}
impl State {
#[track_caller]
fn expect(&self, expected: State) {
if *self != expected {
tracing::error!("expected state to be {expected:?}, found {self:?}");
}
}
#[track_caller]
fn transition(&mut self, from: State, to: State) {
if *self != from {
tracing::error!(
"invalid state transition to {to:?}. Expected {from:?}, found {self:?}"
);
}
*self = to;
}
}
struct EnsureEventOrder<A> {
inner: A,
state: State,
}
impl<A: ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for EnsureEventOrder<A> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
match cause {
StartCause::Init => self.state.transition(State::NotRunning, State::Suspended),
_ => self.state.transition(State::Waiting, State::Running),
}
self.inner.new_events(event_loop, cause);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::Running);
self.inner.resumed(event_loop);
}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Suspended);
self.inner.suspended(event_loop);
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Waiting);
self.inner.about_to_wait(event_loop);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::NotRunning);
self.inner.exiting(event_loop);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
self.state.expect(State::Running);
self.inner.user_event(event_loop, event);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
self.state.expect(State::Running);
self.inner.window_event(event_loop, window_id, event);
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
self.state.expect(State::Running);
self.inner.device_event(event_loop, device_id, event);
}
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
// TODO: What states are allowed when receiving this?
self.inner.memory_warning(event_loop);
}
}
EnsureEventOrder { inner: handler, state: State::NotRunning }
}
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -240,13 +350,15 @@ impl EventLoop {
/// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
#[cfg(debug_assertions)]
let app = &mut ensure_event_order(app);
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() }
}
@@ -302,14 +414,14 @@ impl EventLoop {
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for EventLoop {
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
}
}
#[cfg(feature = "rwh_05")]
unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
@@ -317,7 +429,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -331,7 +443,7 @@ impl AsFd for EventLoop {
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -428,7 +540,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
/// See [`LoopExiting`][crate::event::Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -513,39 +625,53 @@ unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
}
}
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone)]
pub struct EventLoopProxy {
event_loop_proxy: platform_impl::EventLoopProxy,
/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}
impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.event_loop_proxy.wake_up();
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { event_loop_proxy: self.event_loop_proxy.clone() }
}
}
impl fmt::Debug for EventLoopProxy {
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
self.event_loop_proxy.send_event(event)
}
}
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopProxy { .. }")
}
}
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents {

View File

@@ -19,11 +19,12 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`].
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if
//! desired.
//!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works
@@ -162,7 +163,7 @@
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.0",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -78,7 +78,7 @@ use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
impl EventLoopExtAndroid for EventLoop {}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {}
@@ -119,7 +119,7 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self

View File

@@ -66,9 +66,22 @@
use std::os::raw::c_void;
use crate::event_loop::EventLoop;
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn idiom(&self) -> Idiom {
self.event_loop.idiom()
}
}
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
@@ -366,6 +379,24 @@ pub enum ValidOrientations {
Portrait,
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}
bitflags::bitflags! {
/// The [edges] of a screen.
///

View File

@@ -338,7 +338,7 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;

View File

@@ -5,6 +5,11 @@ use crate::event_loop::EventLoop;
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
@@ -99,15 +104,17 @@ pub trait EventLoopExtPumpEvents {
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_app_events<A: ApplicationHandler>(
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,

View File

@@ -7,6 +7,11 @@ use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -53,14 +58,16 @@ pub trait EventLoopExtRunOnDemand {
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {

View File

@@ -38,7 +38,7 @@ pub trait EventLoopExtWayland {
fn is_wayland(&self) -> bool;
}
impl EventLoopExtWayland for EventLoop {
impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
@@ -57,7 +57,7 @@ pub trait EventLoopBuilderExtWayland {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtWayland for EventLoopBuilder {
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);

View File

@@ -155,6 +155,11 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Initializes the winit event loop.
///
/// Unlike
@@ -177,57 +182,15 @@ pub trait EventLoopExtWebSys {
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn set_poll_strategy(&self, strategy: PollStrategy);
/// Gets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
}
impl EventLoopExtWebSys for EventLoop {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.event_loop.set_poll_strategy(strategy);
}
fn poll_strategy(&self) -> PollStrategy {
self.event_loop.poll_strategy()
}
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.event_loop.set_wait_until_strategy(strategy);
}
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.event_loop.wait_until_strategy()
}
}
pub trait ActiveEventLoopExtWebSys {
@@ -245,20 +208,6 @@ pub trait ActiveEventLoopExtWebSys {
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
/// cursor has completely finished loading.
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
@@ -279,16 +228,6 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy()
}
#[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.p.set_wait_until_strategy(strategy);
}
#[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.p.wait_until_strategy()
}
}
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
@@ -317,29 +256,6 @@ pub enum PollStrategy {
Scheduler,
}
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WaitUntilStrategy {
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy is commonly not affected by browser throttling unless the window is not
/// focused.
///
/// This is the default strategy.
///
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
#[default]
Scheduler,
/// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker].
///
/// This strategy is commonly not affected by browser throttling regardless of window focus.
///
/// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
Worker,
}
pub trait CustomCursorExtWebSys {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;

View File

@@ -227,7 +227,7 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static;
}
impl EventLoopBuilderExtWindows for EventLoopBuilder {
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;

View File

@@ -105,7 +105,7 @@ pub trait EventLoopExtX11 {
fn is_x11(&self) -> bool;
}
impl EventLoopExtX11 for EventLoop {
impl<T: 'static> EventLoopExtX11 for EventLoop<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
@@ -124,7 +124,7 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtX11 for EventLoopBuilder {
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);

View File

@@ -1,9 +1,11 @@
#![cfg(android_platform)]
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
@@ -42,6 +44,41 @@ fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
struct PeekableReceiver<T> {
recv: mpsc::Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: mpsc::Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
},
Err(mpsc::TryRecvError::Empty) => false,
Err(mpsc::TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
},
}
}
pub fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
}
#[derive(Clone)]
struct SharedFlagSetter {
flag: Arc<AtomicBool>,
@@ -97,12 +134,13 @@ impl RedrawRequester {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
proxy_wake_up: Arc<AtomicBool>,
loop_running: bool, // Dispatched `NewEvents<Init>`
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, // must wake looper whenever something gets sent
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
cause: StartCause,
@@ -122,11 +160,11 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let (user_events_sender, user_events_receiver) = mpsc::channel();
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
@@ -149,7 +187,8 @@ impl EventLoop {
_marker: PhantomData,
},
redraw_flag,
proxy_wake_up,
user_events_sender,
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
loop_running: false,
running: false,
pending_redraw: false,
@@ -159,7 +198,7 @@ impl EventLoop {
})
}
fn single_iteration<A: ApplicationHandler>(
fn single_iteration<A: ApplicationHandler<T>>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
@@ -278,8 +317,11 @@ impl EventLoop {
},
}
if self.proxy_wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(self.window_target());
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(self.window_target(), event);
}
}
if self.running {
@@ -311,7 +353,7 @@ impl EventLoop {
self.pending_redraw = pending_redraw;
}
fn handle_input_event<A: ApplicationHandler>(
fn handle_input_event<A: ApplicationHandler<T>>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
@@ -418,11 +460,11 @@ impl EventLoop {
input_status
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
@@ -441,7 +483,7 @@ impl EventLoop {
}
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
@@ -475,7 +517,7 @@ impl EventLoop {
}
}
fn poll_events_with_timeout<A: ApplicationHandler>(
fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
@@ -484,22 +526,21 @@ impl EventLoop {
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout = if self.running
&& (self.pending_redraw || self.proxy_wake_up.load(Ordering::Relaxed))
{
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
timeout =
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) {
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
};
min_timeout(control_flow_timeout, timeout)
};
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
@@ -517,7 +558,7 @@ impl EventLoop {
// We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw && !self.proxy_wake_up.load(Ordering::Relaxed))
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming())
{
return;
}
@@ -551,9 +592,9 @@ impl EventLoop {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
proxy_wake_up: self.proxy_wake_up.clone(),
user_events_sender: self.user_events_sender.clone(),
waker: self.android_app.create_waker(),
}
}
@@ -567,16 +608,25 @@ impl EventLoop {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
waker: AndroidAppWaker,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, Ordering::Relaxed);
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
waker: self.waker.clone(),
}
}
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?;
self.waker.wake();
Ok(())
}
}

View File

@@ -1,11 +0,0 @@
//! Apple/Darwin-specific implementations
#[cfg(target_os = "macos")]
mod appkit;
#[cfg(not(target_os = "macos"))]
mod uikit;
#[cfg(target_os = "macos")]
pub use self::appkit::*;
#[cfg(not(target_os = "macos"))]
pub use self::uikit::*;

View File

@@ -1,60 +0,0 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::UIApplication;
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
use crate::event::Event;
declare_class!(
pub struct AppDelegate;
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for AppDelegate {}
// UIApplicationDelegate protocol
unsafe impl AppDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
app_state::terminated(application);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);

View File

@@ -0,0 +1,103 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2_ui_kit::{UIApplication, UIWindow};
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::event::{Event, WindowEvent};
use crate::window::WindowId as RootWindowId;
declare_class!(
pub struct AppDelegate;
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for AppDelegate {}
// UIApplicationDelegate protocol
unsafe impl AppDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);
impl AppDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -10,7 +10,7 @@ use std::{fmt, mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopDefaultMode, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
@@ -20,7 +20,7 @@ use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use objc2_ui_kit::{UICoordinateSpace, UIView};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalSize;
@@ -40,9 +40,12 @@ macro_rules! bug_assert {
};
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event, &RootActiveEventLoop)>,
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop,
}
@@ -56,14 +59,14 @@ impl fmt::Debug for EventLoopHandler {
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event) {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event),
StaticEvent(Event<HandlePendingUserEvents>),
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -85,7 +88,7 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event {
impl Event<HandlePendingUserEvents> {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
@@ -622,7 +625,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
drop(this);
handler.handle_event(Event::UserWakeUp);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
loop {
let mut this = AppState::get_mut(mtm);
@@ -655,32 +658,10 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
handler.handle_event(Event::UserWakeUp);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
}
}
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
@@ -715,27 +696,7 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
handle_nonuser_events(mtm, events);
pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
@@ -795,25 +756,25 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
std::f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopDefaultMode);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
EventLoopWaker { timer }
}
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {

View File

@@ -2,26 +2,28 @@ use std::collections::VecDeque;
use std::ffi::{c_char, c_int, c_void};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use super::app_state::EventLoopHandler;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::ios::Idiom;
use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource};
use super::app_delegate::AppDelegate;
@@ -107,10 +109,10 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<A: ApplicationHandler>(
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
proxy_wake_up: Arc<AtomicBool>,
) -> impl FnMut(Event, &RootActiveEventLoop) + '_ {
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
@@ -119,9 +121,9 @@ fn map_user_event<A: ApplicationHandler>(
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserWakeUp => {
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
app.proxy_wake_up(window_target);
Event::UserEvent(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
}
},
Event::Suspended => app.suspended(window_target),
@@ -132,19 +134,20 @@ fn map_user_event<A: ApplicationHandler>(
}
}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
proxy_wake_up: Arc<AtomicBool>,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop, EventLoopError> {
) -> Result<EventLoop<T>, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
@@ -157,19 +160,20 @@ impl EventLoop {
SINGLETON_INIT = true;
}
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let proxy_wake_up = Arc::new(AtomicBool::new(false));
Ok(EventLoop {
mtm,
proxy_wake_up,
sender,
receiver,
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
})
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -179,12 +183,12 @@ impl EventLoop {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(app, self.proxy_wake_up.clone());
let handler = map_user_event(app, self.receiver);
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
>(Box::new(handler))
};
@@ -212,8 +216,8 @@ impl EventLoop {
unreachable!()
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -221,21 +225,35 @@ impl EventLoop {
}
}
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Clone for EventLoopProxy {
fn clone(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => Idiom::Unspecified,
}
}
}
impl Drop for EventLoopProxy {
pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
@@ -244,8 +262,8 @@ impl Drop for EventLoopProxy {
}
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -265,22 +283,24 @@ impl EventLoopProxy {
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { sender, source }
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}
@@ -347,7 +367,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::MIN,
CFIndex::min_value(),
control_flow_begin_handler,
ptr::null_mut(),
);
@@ -367,7 +387,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::MAX,
CFIndex::max_value(),
control_flow_end_handler,
ptr::null_mut(),
);

View File

@@ -1,3 +1,4 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;

View File

@@ -9,9 +9,9 @@ use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::platform_impl::platform::app_state;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]

View File

@@ -14,9 +14,9 @@ use objc2_ui_kit::{
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use super::DEVICE_ID;
use crate::dpi::PhysicalPosition;
use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
use crate::platform_impl::platform::DEVICE_ID;
use crate::window::{WindowAttributes, WindowId as RootWindowId};
pub struct WinitViewState {

View File

@@ -17,13 +17,15 @@ use tracing::{debug, warn};
use super::app_state::EventWrapper;
use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::platform_impl::platform::{
app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle,
};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,

View File

@@ -21,7 +21,7 @@ use smol_str::SmolStr;
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event_loop::{AsyncRequestSerial, ControlFlow, DeviceEvents};
use crate::event_loop::{AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::icon::Icon;
use crate::keyboard::Key;
#[cfg(x11_platform)]
@@ -691,22 +691,27 @@ unsafe extern "C" fn x_error_callback(
0
}
pub enum EventLoop {
pub enum EventLoop<T: 'static> {
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop>),
Wayland(Box<wayland::EventLoop<T>>),
#[cfg(x11_platform)]
X(x11::EventLoop),
X(x11::EventLoop<T>),
}
#[derive(Clone)]
pub enum EventLoopProxy {
pub enum EventLoopProxy<T: 'static> {
#[cfg(x11_platform)]
X(x11::EventLoopProxy),
X(x11::EventLoopProxy<T>),
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy),
Wayland(wayland::EventLoopProxy<T>),
}
impl EventLoop {
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -765,12 +770,12 @@ impl EventLoop {
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
@@ -789,22 +794,22 @@ impl EventLoop {
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
@@ -817,21 +822,21 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
}
}

View File

@@ -5,6 +5,7 @@ use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -39,7 +40,7 @@ use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
@@ -47,8 +48,13 @@ pub struct EventLoop {
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
/// Event loop proxy
event_loop_proxy: EventLoopProxy,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
@@ -65,8 +71,8 @@ pub struct EventLoop {
event_loop: calloop::EventLoop<'static, WinitState>,
}
impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
@@ -109,12 +115,16 @@ impl EventLoop {
)?;
// Setup the user proxy.
let (ping, ping_source) = calloop::ping::make_ping().unwrap();
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let result = event_loop
.handle()
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true;
winit_state.proxy_wake_up = true;
.insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
@@ -152,7 +162,8 @@ impl EventLoop {
window_ids: Vec::new(),
connection,
wayland_dispatcher,
event_loop_proxy: EventLoopProxy::new(ping),
user_events_sender,
pending_user_events,
event_loop,
window_target: RootActiveEventLoop {
p: PlatformActiveEventLoop::Wayland(window_target),
@@ -163,11 +174,11 @@ impl EventLoop {
Ok(event_loop)
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
@@ -194,7 +205,7 @@ impl EventLoop {
exit
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
@@ -222,7 +233,7 @@ impl EventLoop {
}
}
pub fn poll_events_with_timeout<A: ApplicationHandler>(
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
@@ -291,7 +302,7 @@ impl EventLoop {
self.single_iteration(app, cause);
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
@@ -310,9 +321,10 @@ impl EventLoop {
app.resumed(&self.window_target);
}
// Indicate user wake up.
if self.with_state(|state| mem::take(&mut state.proxy_wake_up)) {
app.proxy_wake_up(&self.window_target);
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
app.user_event(&self.window_target, user_event);
}
// Drain the pending compositor updates.
@@ -513,8 +525,8 @@ impl EventLoop {
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
}
#[inline]
@@ -576,13 +588,13 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}

View File

@@ -1,19 +1,28 @@
//! An event loop proxy.
use sctk::reexports::calloop::ping::Ping;
use std::sync::mpsc::SendError;
use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::EventLoopClosed;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: Sender<T>,
}
impl EventLoopProxy {
pub fn new(ping: Ping) -> Self {
Self { ping }
}
pub fn wake_up(&self) {
self.ping.ping();
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_events_sender: self.user_events_sender.clone() }
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(user_events_sender: Sender<T>) -> Self {
Self { user_events_sender }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error))
}
}

View File

@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub(crate) window_events: Vec<Event>,
pub window_events: Vec<Event<()>>,
}
impl EventSink {
@@ -47,7 +47,7 @@ impl EventSink {
}
#[inline]
pub(crate) fn drain(&mut self) -> Drain<'_, Event> {
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
self.window_events.drain(..)
}
}

View File

@@ -1,3 +1,5 @@
#![cfg(wayland_platform)]
//! Winit's Wayland backend.
use std::fmt::Display;

View File

@@ -115,9 +115,6 @@ pub struct WinitState {
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
/// Whether the user initiated a wake up.
pub proxy_wake_up: bool,
}
impl WinitState {
@@ -195,7 +192,6 @@ impl WinitState {
loop_handle,
// Make it true by default.
dispatched_events: true,
proxy_wake_up: false,
})
}

View File

@@ -72,9 +72,9 @@ pub struct EventProcessor {
}
impl EventProcessor {
pub(crate) fn process_event<F>(&mut self, xev: &mut XEvent, mut callback: F)
pub fn process_event<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
self.process_xevent(xev, &mut callback);
@@ -139,9 +139,9 @@ impl EventProcessor {
}
}
fn process_xevent<F>(&mut self, xev: &mut XEvent, mut callback: F)
fn process_xevent<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let event_type = xev.get_type();
@@ -387,9 +387,9 @@ impl EventProcessor {
}
}
fn client_message<F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
fn client_message<T: 'static, F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -434,7 +434,7 @@ impl EventProcessor {
let flags = xev.data.get_long(1);
let version = flags >> 24;
self.dnd.version = Some(version);
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
if !has_more_types {
let type_list = vec![
xev.data.get_long(2) as xproto::Atom,
@@ -552,9 +552,9 @@ impl EventProcessor {
}
}
fn selection_notify<F>(&mut self, xev: &XSelectionEvent, mut callback: F)
fn selection_notify<T: 'static, F>(&mut self, xev: &XSelectionEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -586,9 +586,9 @@ impl EventProcessor {
}
}
fn configure_notify<F>(&self, xev: &XConfigureEvent, mut callback: F)
fn configure_notify<T: 'static, F>(&self, xev: &XConfigureEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -781,9 +781,9 @@ impl EventProcessor {
});
}
fn map_notify<F>(&self, xev: &XMapEvent, mut callback: F)
fn map_notify<T: 'static, F>(&self, xev: &XMapEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
@@ -799,9 +799,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn destroy_notify<F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
fn destroy_notify<T: 'static, F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -823,9 +823,9 @@ impl EventProcessor {
callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Destroyed });
}
fn property_notify<F>(&mut self, xev: &XPropertyEvent, mut callback: F)
fn property_notify<T: 'static, F>(&mut self, xev: &XPropertyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let atoms = wt.x_connection().atoms();
@@ -838,9 +838,9 @@ impl EventProcessor {
}
}
fn visibility_notify<F>(&self, xev: &XVisibilityEvent, mut callback: F)
fn visibility_notify<T: 'static, F>(&self, xev: &XVisibilityEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let xwindow = xev.window as xproto::Window;
@@ -855,9 +855,9 @@ impl EventProcessor {
});
}
fn expose<F>(&self, xev: &XExposeEvent, mut callback: F)
fn expose<T: 'static, F>(&self, xev: &XExposeEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
@@ -871,9 +871,13 @@ impl EventProcessor {
}
}
fn xinput_key_input<F>(&mut self, xev: &mut XKeyEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput_key_input<T: 'static, F>(
&mut self,
xev: &mut XKeyEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -985,13 +989,13 @@ impl EventProcessor {
}
}
fn send_synthic_modifier_from_core<F>(
fn send_synthic_modifier_from_core<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let keymap = match self.xkb_context.keymap_mut() {
Some(keymap) => keymap,
@@ -1018,9 +1022,13 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_button_input<F>(&self, event: &XIDeviceEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_button_input<T: 'static, F>(
&self,
event: &XIDeviceEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window_id = mkwid(event.event as xproto::Window);
@@ -1071,9 +1079,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_mouse_motion<F>(&self, event: &XIDeviceEvent, mut callback: F)
fn xinput2_mouse_motion<T: 'static, F>(&self, event: &XIDeviceEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1149,9 +1157,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_enter<F>(&self, event: &XIEnterEvent, mut callback: F)
fn xinput2_mouse_enter<T: 'static, F>(&self, event: &XIEnterEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1194,9 +1202,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_left<F>(&self, event: &XILeaveEvent, mut callback: F)
fn xinput2_mouse_left<T: 'static, F>(&self, event: &XILeaveEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = event.event as xproto::Window;
@@ -1217,9 +1225,9 @@ impl EventProcessor {
}
}
fn xinput2_focused<F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
fn xinput2_focused<T: 'static, F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1276,9 +1284,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_unfocused<F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
fn xinput2_unfocused<T: 'static, F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1328,9 +1336,13 @@ impl EventProcessor {
}
}
fn xinput2_touch<F>(&mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_touch<T: 'static, F>(
&mut self,
xev: &XIDeviceEvent,
phase: TouchPhase,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1370,9 +1382,13 @@ impl EventProcessor {
}
}
fn xinput2_raw_button_input<F>(&self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_raw_button_input<T: 'static, F>(
&self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1388,9 +1404,9 @@ impl EventProcessor {
}
}
fn xinput2_raw_mouse_motion<F>(&self, xev: &XIRawEvent, mut callback: F)
fn xinput2_raw_mouse_motion<T: 'static, F>(&self, xev: &XIRawEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1448,9 +1464,13 @@ impl EventProcessor {
}
}
fn xinput2_raw_key_input<F>(&mut self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn xinput2_raw_key_input<T: 'static, F>(
&mut self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1470,9 +1490,9 @@ impl EventProcessor {
});
}
fn xinput2_hierarchy_changed<F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
fn xinput2_hierarchy_changed<T: 'static, F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1497,9 +1517,9 @@ impl EventProcessor {
}
}
fn xkb_event<F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
fn xkb_event<T: 'static, F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
match xev.xkb_type {
@@ -1576,14 +1596,14 @@ impl EventProcessor {
}
}
pub(crate) fn update_mods_from_xinput2_event<F>(
pub fn update_mods_from_xinput2_event<T: 'static, F>(
&mut self,
mods: &XIModifierState,
group: &XIModifierState,
force: bool,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
if let Some(state) = self.xkb_context.state_mut() {
state.update_modifiers(
@@ -1607,9 +1627,12 @@ impl EventProcessor {
}
}
fn update_mods_from_query<F>(&mut self, window_id: crate::window::WindowId, mut callback: F)
where
F: FnMut(&RootAEL, Event),
fn update_mods_from_query<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
@@ -1638,13 +1661,13 @@ impl EventProcessor {
self.send_modifiers(window_id, mods.into(), true, &mut callback)
}
pub(crate) fn update_mods_from_core_event<F>(
pub fn update_mods_from_core_event<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let xkb_mask = self.xkb_mod_mask_from_core(state);
let xkb_state = match self.xkb_context.state_mut() {
@@ -1717,7 +1740,7 @@ impl EventProcessor {
///
/// The event won't be sent when the `modifiers` match the previously `sent` modifiers value,
/// unless `force` is passed. The `force` should be passed when the active window changes.
fn send_modifiers<F: FnMut(&RootAEL, Event)>(
fn send_modifiers<T: 'static, F: FnMut(&RootAEL, Event<T>)>(
&self,
window_id: crate::window::WindowId,
modifiers: ModifiersState,
@@ -1735,14 +1758,14 @@ impl EventProcessor {
}
}
fn handle_pressed_keys<F>(
fn handle_pressed_keys<T: 'static, F>(
target: &RootAEL,
window_id: crate::window::WindowId,
state: ElementState,
xkb_context: &mut Context,
callback: &mut F,
) where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
@@ -1777,9 +1800,9 @@ impl EventProcessor {
}
}
fn process_dpi_change<F>(&self, callback: &mut F)
fn process_dpi_change<T: 'static, F>(&self, callback: &mut F)
where
F: FnMut(&RootAEL, Event),
F: FnMut(&RootAEL, Event<T>),
{
let wt = Self::window_target(&self.target);
wt.xconn.reload_database().expect("failed to reload Xft database");

View File

@@ -158,9 +158,7 @@ struct PreeditCallbacks {
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
preedit_start_callback as usize,
)
mem::transmute(preedit_start_callback as usize)
});
let done_callback = create_xim_callback(client_data, preedit_done_callback);
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);

View File

@@ -1,3 +1,5 @@
#![cfg(x11_platform)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
@@ -9,7 +11,7 @@ use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::{fmt, mem, ptr, slice, str};
use std::{fmt, ptr, slice, str};
use calloop::generic::Generic;
use calloop::ping::Ping;
@@ -28,7 +30,7 @@ use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::{min_timeout, WindowId};
@@ -81,11 +83,12 @@ impl<T> Clone for WakeSender<T> {
}
impl<T> WakeSender<T> {
pub fn send(&self, t: T) {
let res = self.sender.send(t);
pub fn send(&self, t: T) -> Result<(), EventLoopClosed<T>> {
let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0));
if res.is_ok() {
self.waker.ping();
}
res
}
}
@@ -140,13 +143,15 @@ pub struct ActiveEventLoop {
device_events: Cell<DeviceEvents>,
}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
loop_running: bool,
event_loop: Loop<'static, EventLoopState>,
waker: calloop::ping::Ping,
event_processor: EventProcessor,
redraw_receiver: PeekableReceiver<WindowId>,
user_receiver: PeekableReceiver<T>,
activation_receiver: PeekableReceiver<ActivationToken>,
event_loop_proxy: EventLoopProxy,
user_sender: Sender<T>,
/// The current state of the event loop.
state: EventLoopState,
@@ -157,13 +162,20 @@ type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial);
struct EventLoopState {
/// The latest readiness state for the x11 file descriptor
x11_readiness: Readiness,
/// User requested a wake up.
proxy_wake_up: bool,
}
impl EventLoop {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop {
pub struct EventLoopProxy<T: 'static> {
user_sender: WakeSender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_sender: self.user_sender.clone() }
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
let root = xconn.default_root().root;
let atoms = xconn.atoms();
@@ -267,16 +279,7 @@ impl EventLoop {
let (activation_token_sender, activation_token_channel) = mpsc::channel();
// Create a channel for sending user events.
let (user_waker, user_waker_source) =
calloop::ping::make_ping().expect("Failed to create user event loop waker.");
event_loop
.handle()
.insert_source(user_waker_source, move |_, _, state| {
// No extra handling is required, we just need to wake-up.
state.proxy_wake_up = true;
})
.expect("Failed to register the event loop waker source");
let event_loop_proxy = EventLoopProxy::new(user_waker);
let (user_sender, user_channel) = mpsc::channel();
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
@@ -357,27 +360,31 @@ impl EventLoop {
EventLoop {
loop_running: false,
event_loop,
waker,
event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
event_loop_proxy,
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
state: EventLoopState { x11_readiness: Readiness::EMPTY },
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: WakeSender { sender: self.user_sender.clone(), waker: self.waker.clone() },
}
}
pub(crate) fn window_target(&self) -> &RootAEL {
&self.event_processor.target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
@@ -407,7 +414,7 @@ impl EventLoop {
exit
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
@@ -437,11 +444,11 @@ impl EventLoop {
fn has_pending(&mut self) -> bool {
self.event_processor.poll()
|| self.state.proxy_wake_up
|| self.user_receiver.has_incoming()
|| self.redraw_receiver.has_incoming()
}
pub fn poll_events_with_timeout<A: ApplicationHandler>(
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
@@ -506,7 +513,7 @@ impl EventLoop {
self.single_iteration(app, cause);
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
@@ -541,8 +548,10 @@ impl EventLoop {
}
// Empty the user event buffer
if mem::take(&mut self.state.proxy_wake_up) {
app.proxy_wake_up(&self.event_processor.target);
{
while let Ok(event) = self.user_receiver.try_recv() {
app.user_event(&self.event_processor.target, event);
}
}
// Empty the redraw requests
@@ -567,19 +576,19 @@ impl EventLoop {
app.about_to_wait(&self.event_processor.target);
}
fn drain_events<A: ApplicationHandler>(&mut self, app: &mut A) {
fn drain_events<A: ApplicationHandler<T>>(&mut self, app: &mut A) {
let mut xev = MaybeUninit::uninit();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |window_target, event: Event| {
self.event_processor.process_event(&mut xev, |window_target, event: Event<T>| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid);
window_target.redraw_sender.send(wid).unwrap();
} else {
match event {
Event::WindowEvent { window_id, event } => {
@@ -616,13 +625,13 @@ impl EventLoop {
}
}
impl AsFd for EventLoop {
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl AsRawFd for EventLoop {
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
@@ -722,9 +731,9 @@ impl ActiveEventLoop {
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.ping.ping();
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| EventLoopClosed(e.0))
}
}
@@ -808,17 +817,6 @@ impl Drop for Window {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxy {
fn new(ping: Ping) -> Self {
Self { ping }
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {

View File

@@ -876,11 +876,11 @@ impl UnownedWindow {
/// Refresh the API for the given monitor.
#[inline]
pub(super) fn refresh_dpi_for_monitor(
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event),
mut callback: impl FnMut(Event<T>),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
@@ -1805,7 +1805,9 @@ impl UnownedWindow {
#[inline]
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let serial = AsyncRequestSerial::get();
self.activation_sender.send((self.id(), serial));
self.activation_sender
.send((self.id(), serial))
.expect("activation token channel should never be closed");
Ok(serial)
}
@@ -1816,7 +1818,7 @@ impl UnownedWindow {
#[inline]
pub fn request_redraw(&self) {
self.redraw_sender.send(WindowId(self.xwindow as _));
self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap();
}
#[inline]

View File

@@ -4,8 +4,7 @@ use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::ApplicationDelegate;
use super::DEVICE_ID;
use super::app_delegate::ApplicationDelegate;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
@@ -58,47 +57,27 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
axis: 0,
value: delta_x,
});
});
delegate.queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
}
if delta_y != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
axis: 1,
value: delta_y,
});
})
delegate.queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
}
if delta_x != 0.0 || delta_y != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
});
delegate.queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y) });
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
delegate.queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Released,
});
delegate.queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
});
},
_ => (),

View File

@@ -1,32 +1,40 @@
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::rc::Weak;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSSize};
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::{menu, WindowId};
use super::window::WinitWindow;
use super::{menu, WindowId, DEVICE_ID};
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
pub(super) struct AppState {
activation_policy: NSApplicationActivationPolicy,
struct Policy(NSApplicationActivationPolicy);
impl Default for Policy {
fn default() -> Self {
Self(NSApplicationActivationPolicy::Regular)
}
}
#[derive(Debug, Default)]
pub(super) struct State {
activation_policy: Policy,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
proxy_wake_up: Arc<AtomicBool>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -42,6 +50,7 @@ pub(super) struct AppState {
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_events: RefCell<VecDeque<QueuedEvent>>,
pending_redraw: RefCell<Vec<WindowId>>,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those.
@@ -58,20 +67,62 @@ declare_class!(
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = AppState;
type Ivars = State;
}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
unsafe impl NSApplicationDelegate for ApplicationDelegate {
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
#[method(applicationDidFinishLaunching:)]
fn app_did_finish_launching(&self, notification: &NSNotification) {
self.did_finish_launching(notification)
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy.0);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
#[method(applicationWillTerminate:)]
fn app_will_terminate(&self, notification: &NSNotification) {
self.will_terminate(notification)
fn will_terminate(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
}
);
@@ -80,83 +131,18 @@ impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: NSApplicationActivationPolicy,
proxy_wake_up: Arc<AtomicBool>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
proxy_wake_up,
let this = mtm.alloc().set_ivars(State {
activation_policy: Policy(activation_policy),
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
..Default::default()
});
unsafe { msg_send_id![super(this), init] }
}
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
fn did_finish_launching(&self, _notification: &NSNotification) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
fn will_terminate(&self, _notification: &NSNotification) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
@@ -173,7 +159,7 @@ impl ApplicationDelegate {
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: &mut dyn ApplicationHandler,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop),
closure: impl FnOnce() -> R,
) -> R {
self.ivars().event_handler.set(handler, closure)
@@ -207,9 +193,8 @@ impl ApplicationDelegate {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(&self) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.handle_event(Event::Suspended);
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
self.set_stop_on_redraw(false);
@@ -250,13 +235,38 @@ impl ApplicationDelegate {
self.ivars().control_flow.get()
}
pub fn queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.ivars()
.pending_events
.borrow_mut()
.push_back(QueuedEvent::WindowEvent(window_id, event));
}
pub fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
}
pub fn queue_static_scale_factor_changed_event(
&self,
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
}
pub fn handle_redraw(&self, window_id: WindowId) {
let mtm = MainThreadMarker::from(self);
// Redraw request might come out of order from the OS.
// -> Don't go back into the event handler when our callstack originates from there
if !self.ivars().event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -274,46 +284,19 @@ impl ApplicationDelegate {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.ivars().run_loop.wakeup();
unsafe { RunLoop::get() }.wakeup();
}
#[track_caller]
pub fn maybe_queue_with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop) + 'static,
) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
// result in an event being queued, and applied at a later point.
//
// However, it is not documented which actions do this, and which ones are done immediately,
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.ivars().event_handler.in_use() {
self.with_handler(callback);
} else {
tracing::debug!("had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || {
this.with_handler(callback);
});
}
}
#[track_caller]
fn with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
) {
let event_loop = ActiveEventLoop::new_root(self.retain());
self.ivars().event_handler.handle(callback, &event_loop);
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
/// dispatch `NewEvents(Init)` + `Resumed`
pub fn dispatch_init_events(&self) {
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
self.handle_event(Event::NewEvents(StartCause::Init));
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
// applications don't themselves have a formal suspend/resume lifecycle.
self.with_handler(|app, event_loop| app.resumed(event_loop));
self.handle_event(Event::Resumed);
}
// Called by RunLoopObserver after finishing waiting for new events
@@ -323,7 +306,8 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.is_running() {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
@@ -345,7 +329,7 @@ impl ApplicationDelegate {
},
};
self.with_handler(|app, event_loop| app.new_events(event_loop, cause));
self.handle_event(Event::NewEvents(cause));
}
// Called by RunLoopObserver before waiting for new events
@@ -355,23 +339,67 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.is_running() {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
let events = mem::take(&mut *self.ivars().pending_events.borrow_mut());
for event in events {
match event {
QueuedEvent::WindowEvent(window_id, event) => {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event,
});
},
QueuedEvent::DeviceEvent(event) => {
self.handle_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
},
QueuedEvent::ScaleFactorChanged { window, suggested_size, scale_factor } => {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let scale_factor_changed_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
};
self.handle_event(scale_factor_changed_event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
let resized_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
self.handle_event(resized_event);
},
}
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
}
self.with_handler(|app, event_loop| {
app.about_to_wait(event_loop);
});
self.handle_event(Event::AboutToWait);
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
@@ -393,6 +421,20 @@ impl ApplicationDelegate {
}
}
#[derive(Debug)]
pub(crate) enum QueuedEvent {
WindowEvent(WindowId, WindowEvent),
DeviceEvent(DeviceEvent),
ScaleFactorChanged {
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
/// Returns the minimum `Option<Instant>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)

View File

@@ -7,12 +7,12 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_foundation::{run_on_main, NSPoint};
use smol_str::SmolStr;
use super::ffi;
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use crate::platform_impl::platform::ffi;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {

View File

@@ -1,34 +1,31 @@
use std::cell::RefCell;
use std::{fmt, mem};
use crate::application::ApplicationHandler;
use super::app_delegate::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
#[derive(Default)]
struct EventHandlerData {
#[allow(clippy::type_complexity)]
handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
}
impl fmt::Debug for EventHandlerData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventHandlerData").finish_non_exhaustive()
}
}
#[derive(Debug, Default)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
inner: RefCell<Option<EventHandlerData>>,
}
impl EventHandler {
pub(crate) fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure.
///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
@@ -36,7 +33,7 @@ impl EventHandler {
/// from within the closure.
pub(crate) fn set<'handler, R>(
&self,
app: &'handler mut dyn ApplicationHandler,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
@@ -47,9 +44,9 @@ impl EventHandler {
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
>(Box::new(handler))
};
match self.inner.try_borrow_mut().as_deref_mut() {
@@ -57,7 +54,7 @@ impl EventHandler {
unreachable!("tried to set handler while another was already set");
},
Ok(data @ None) => {
*data = Some(handler);
*data = Some(EventHandlerData { handler });
},
Err(_) => {
unreachable!("tried to set handler that is currently in use");
@@ -104,20 +101,24 @@ impl EventHandler {
self.inner.try_borrow().is_err()
}
pub(crate) fn handle(
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle_event(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
event: Event<HandlePendingUserEvents>,
event_loop: &RootActiveEventLoop,
) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(user_app)) => {
Ok(Some(EventHandlerData { handler })) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(*user_app, event_loop);
(handler)(event, event_loop);
},
Ok(None) => {
// `NSApplication`, our app delegate and this handler are all

View File

@@ -6,13 +6,12 @@ use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopDefaultMode, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
@@ -22,16 +21,19 @@ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::WinitApplication;
use super::app_state::ApplicationDelegate;
use super::cursor::CustomCursor;
use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents};
use super::event::dummy_event;
use super::monitor::{self, MonitorHandle};
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents};
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
#[derive(Default)]
@@ -154,7 +156,32 @@ impl ActiveEventLoop {
}
}
pub struct EventLoop {
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
receiver: Rc<mpsc::Receiver<T>>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience.
///
/// We intentionally don't store `WinitApplication` since we want to have
@@ -166,7 +193,9 @@ pub struct EventLoop {
/// keep it around here as well.
delegate: Retained<ApplicationDelegate>,
proxy_wake_up: Arc<AtomicBool>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: RootWindowTarget,
panic_info: Rc<PanicInfo>,
@@ -189,7 +218,7 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
impl EventLoop {
impl<T> EventLoop<T> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -211,13 +240,9 @@ impl EventLoop {
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
};
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let delegate = ApplicationDelegate::new(
mtm,
activation_policy,
proxy_wake_up.clone(),
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
@@ -227,16 +252,18 @@ impl EventLoop {
});
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
setup_control_flow_observers(Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
app,
delegate: delegate.clone(),
sender,
receiver: Rc::new(receiver),
window_target: RootWindowTarget {
p: ActiveEventLoop { delegate, mtm },
_marker: PhantomData,
},
proxy_wake_up,
panic_info,
})
}
@@ -245,7 +272,7 @@ impl EventLoop {
&self.window_target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
@@ -253,11 +280,13 @@ impl EventLoop {
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
self.delegate.set_event_handler(app, || {
let handler = map_user_event(app, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.delegate.set_wait_timeout(None);
@@ -290,12 +319,14 @@ impl EventLoop {
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.delegate.set_event_handler(app, || {
let handler = map_user_event(app, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
@@ -359,8 +390,8 @@ impl EventLoop {
})
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
}
}
@@ -418,15 +449,15 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}
}
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub struct EventLoopProxy<T> {
sender: mpsc::Sender<T>,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
impl Drop for EventLoopProxy {
impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
@@ -434,14 +465,14 @@ impl Drop for EventLoopProxy {
}
}
impl Clone for EventLoopProxy {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy::new(self.proxy_wake_up.clone())
EventLoopProxy::new(self.sender.clone())
}
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
impl<T> EventLoopProxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -461,21 +492,23 @@ impl EventLoopProxy {
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { sender, source }
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}

View File

@@ -2,7 +2,7 @@
mod util;
mod app;
mod app_state;
mod app_delegate;
mod cursor;
mod event;
mod event_handler;

View File

@@ -1,28 +1,21 @@
//! Utilities for working with `CFRunLoop`.
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::Weak;
use std::time::Instant;
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
};
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
@@ -91,20 +84,10 @@ extern "C" fn control_flow_end_handler(
}
}
#[derive(Debug)]
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
pub unsafe fn get() -> Self {
RunLoop(unsafe { CFRunLoopGetMain() })
}
@@ -129,81 +112,11 @@ impl RunLoop {
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
@@ -212,15 +125,16 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
release: None,
copyDescription: None,
};
let run_loop = RunLoop::get();
run_loop.add_observer(
kCFRunLoopAfterWaiting,
CFIndex::MIN,
CFIndex::min_value(),
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::MAX,
CFIndex::max_value(),
control_flow_end_handler,
&mut context as *mut _,
);
@@ -251,8 +165,8 @@ impl Drop for EventLoopWaker {
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
@@ -260,29 +174,31 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
std::f64::MAX,
0.000_000_1,
0,
0,
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
}
impl EventLoopWaker {
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
}

View File

@@ -2,8 +2,7 @@ use tracing::trace;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
};
}

View File

@@ -16,7 +16,7 @@ use objc2_foundation::{
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
@@ -31,7 +31,6 @@ use crate::event::{
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct CursorState {
@@ -271,20 +270,19 @@ declare_class!(
fn set_marked_text(
&self,
string: &NSObject,
selected_range: NSRange,
_selected_range: NSRange,
_replacement_range: NSRange,
) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let (marked_text, preedit_string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
string.string().to_string(),
)
} else {
let string: *const NSObject = string;
@@ -292,7 +290,7 @@ declare_class!(
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
string.to_string(),
)
};
@@ -312,21 +310,16 @@ declare_class!(
self.ivars().ime_state.set(ImeState::Ground);
}
let cursor_range = if string.is_empty() {
// An empty string basically means that there's no preedit, so indicate that by
// sending a `None` cursor range.
// Empty string basically means that there's no preedit, so indicate that by sending
// `None` cursor range.
let cursor_range = if preedit_string.is_empty() {
None
} else {
// Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
Some((preedit_string.len(), preedit_string.len()))
};
// Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
self.queue_event(WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)));
}
#[method(unmarkText)]
@@ -386,7 +379,6 @@ declare_class!(
#[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
@@ -687,9 +679,7 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
);
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -833,10 +823,11 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
}
fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().app_delegate.queue_device_event(event);
}
fn scale_factor(&self) -> f64 {

View File

@@ -1,9 +1,6 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ffi::c_void;
use std::ptr;
use std::sync::{Arc, Mutex};
use core_graphics::display::{CGDisplay, CGPoint};
use monitor::VideoModeHandle;
@@ -11,35 +8,32 @@ use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter,
NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{InnerSizeWriter, WindowEvent};
use crate::event::WindowEvent;
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
WindowAttributes, WindowButtons, WindowLevel,
};
#[derive(Clone, Debug)]
@@ -83,6 +77,8 @@ pub(crate) struct State {
window: Retained<WinitWindow>,
current_theme: Cell<Option<Theme>>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
// This is expressed in native screen coordinates.
@@ -188,17 +184,7 @@ declare_class!(
#[method(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
self.ivars().previous_scale_factor.set(scale_factor);
let mtm = MainThreadMarker::from(self);
let this = self.retain();
RunLoop::main(mtm).queue_closure(move || {
this.handle_scale_factor_changed(scale_factor);
});
self.queue_static_scale_factor_changed_event();
}
#[method(windowDidBecomeKey:)]
@@ -421,66 +407,32 @@ declare_class!(
}
}
// Key-Value Observing
unsafe impl WindowDelegate {
#[method(observeValueForKeyPath:ofObject:change:context:)]
fn observe_value(
&self,
key_path: Option<&NSString>,
_object: Option<&AnyObject>,
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
_context: *mut c_void,
) {
trace_scope!("observeValueForKeyPath:ofObject:change:context:");
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
// in the future we might want to observe other key paths.
if key_path == Some(ns_string!("effectiveAppearance")) {
let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// Observe theme change
#[method(effectiveAppearanceDidChange:)]
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
trace_scope!("effectiveAppearanceDidChange:");
unsafe {
self.performSelectorOnMainThread_withObject_waitUntilDone(
sel!(effectiveAppearanceDidChangedOnMainThread:),
sender,
false,
)
};
}
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old;
let old: *const NSAppearance = old.cast();
let old: &NSAppearance = unsafe { &*old };
let new: *const AnyObject = new;
let new: *const NSAppearance = new.cast();
let new: &NSAppearance = unsafe { &*new };
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
// Ignore the change if the window's theme is customized by the user (since in that
// case the `effectiveAppearance` is only emitted upon said customization, and then
// it's triggered directly by a user action, and we don't want to emit the event).
if unsafe { self.window().appearance() }.is_some() {
return;
}
let old = appearance_to_theme(old);
let new = appearance_to_theme(new);
// Check that the theme changed in Winit's terms (the theme might have changed on
// other parameters, such as level of contrast, but the event should not be emitted
// in those cases).
if old == new {
return;
}
self.queue_event(WindowEvent::ThemeChanged(new));
} else {
panic!("unknown observed keypath {key_path:?}");
#[method(effectiveAppearanceDidChangedOnMainThread:)]
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
let theme = get_ns_theme(mtm);
let old_theme = self.ivars().current_theme.replace(Some(theme));
if old_theme != Some(theme) {
self.queue_event(WindowEvent::ThemeChanged(theme));
}
}
}
);
impl Drop for WindowDelegate {
fn drop(&mut self) {
unsafe {
self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance"));
}
}
}
fn new_window(
app_delegate: &ApplicationDelegate,
attrs: &WindowAttributes,
@@ -649,8 +601,6 @@ fn new_window(
if attrs.transparent {
window.setOpaque(false);
// See `set_transparent` for details on why we do this.
window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) });
}
// register for drag and drop operations.
@@ -704,13 +654,15 @@ impl WindowDelegate {
let scale_factor = window.backingScaleFactor() as _;
if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) {
unsafe { window.setAppearance(Some(&appearance)) };
}
let current_theme = match attrs.preferred_theme {
Some(theme) => Some(theme),
None => Some(get_ns_theme(mtm)),
};
let delegate = mtm.alloc().set_ivars(State {
app_delegate: app_delegate.retain(),
window: window.retain(),
current_theme: Cell::new(current_theme),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
resize_increments: Cell::new(resize_increments),
@@ -729,23 +681,18 @@ impl WindowDelegate {
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
delegate.queue_static_scale_factor_changed_event();
}
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
// Listen for theme change event.
//
// SAFETY: The observer is un-registered in the `Drop` of the delegate.
// Enable theme change event
let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() };
unsafe {
window.addObserver_forKeyPath_options_context(
notification_center.addObserver_selector_name_object(
&delegate,
ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
ptr::null_mut(),
sel!(effectiveAppearanceDidChange:),
Some(ns_string!("AppleInterfaceThemeChangedNotification")),
None,
)
};
@@ -807,33 +754,24 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
}
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let window = self.window();
fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
let content_size = window.contentRectForFrameRect(window.frame()).size;
self.ivars().previous_scale_factor.set(scale_factor);
let content_size = self.window().contentRectForFrameRect(self.window().frame()).size;
let content_size = LogicalSize::new(content_size.width, content_size.height);
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
self.queue_event(WindowEvent::ScaleFactorChanged {
self.ivars().app_delegate.queue_static_scale_factor_changed_event(
self.window().retain(),
content_size.to_physical(scale_factor),
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
});
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
self.queue_event(WindowEvent::Resized(physical_size));
);
}
fn emit_move_event(&self) {
@@ -861,23 +799,7 @@ impl WindowDelegate {
}
pub fn set_transparent(&self, transparent: bool) {
// This is just a hint for Quartz, it doesn't actually speculate with window alpha.
// Providing a wrong value here could result in visual artifacts, when the window is
// transparent.
self.window().setOpaque(!transparent);
// AppKit draws the window with a background color by default, which is usually really
// nice, but gets in the way when we want to allow the contents of the window to be
// transparent, as in that case, the transparent contents will just be drawn on top of
// the background color. As such, to allow the window to be transparent, we must also set
// the background color to one with an empty alpha channel.
let color = if transparent {
unsafe { NSColor::clearColor() }
} else {
unsafe { NSColor::windowBackgroundColor() }
};
self.window().setBackgroundColor(Some(&color));
self.window().setOpaque(!transparent)
}
pub fn set_blur(&self, blur: bool) {
@@ -976,8 +898,8 @@ impl WindowDelegate {
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
width: f32::MAX as f64,
height: f32::MAX as f64,
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
}));
let scale_factor = self.scale_factor();
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
@@ -1653,24 +1575,20 @@ impl WindowDelegate {
}
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.ivars().current_theme.get()
}
#[inline]
pub fn has_focus(&self) -> bool {
self.window().isKeyWindow()
}
pub fn theme(&self) -> Option<Theme> {
// Note: We could choose between returning the value of `effectiveAppearance` or
// `appearance`, depending on what the user is asking about:
// - "how should I render on this particular frame".
// - "what is the configuration for this window".
//
// We choose the latter for consistency with the `set_theme` call, though it might also be
// useful to expose the former.
Some(appearance_to_theme(unsafe { &*self.window().appearance()? }))
}
pub fn set_theme(&self, theme: Option<Theme>) {
unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) };
let mtm = MainThreadMarker::from(self);
set_ns_theme(theme, mtm);
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
}
#[inline]
@@ -1829,39 +1747,34 @@ impl WindowExtMacOS for WindowDelegate {
const DEFAULT_STANDARD_FRAME: NSRect =
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
fn dark_appearance_name() -> &'static NSString {
// Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
ns_string!("NSAppearanceNameDarkAqua")
}
fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),
]));
if let Some(best_match) = best_match {
if *best_match == *dark_appearance_name() {
Theme::Dark
} else {
Theme::Light
}
} else {
warn!(?appearance, "failed to determine the theme of the appearance");
// Default to light in this case
Theme::Light
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
let app = NSApplication::sharedApplication(mtm);
if !app.respondsToSelector(sel!(effectiveAppearance)) {
return Theme::Light;
}
let appearance = app.effectiveAppearance();
let name = appearance
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
NSString::from_str("NSAppearanceNameAqua"),
NSString::from_str("NSAppearanceNameDarkAqua"),
]))
.unwrap();
match &*name.to_string() {
"NSAppearanceNameDarkAqua" => Theme::Dark,
_ => Theme::Light,
}
}
fn theme_to_appearance(theme: Option<Theme>) -> Option<Retained<NSAppearance>> {
let appearance = match theme? {
Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) },
Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()),
};
if let Some(appearance) = appearance {
Some(appearance)
} else {
warn!(?theme, "could not find appearance for theme");
// Assume system appearance in this case
None
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
let appearance = theme.map(|t| {
let name = match t {
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
};
NSAppearance::appearanceNamed(&name).unwrap()
});
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
}
}

View File

@@ -1,31 +1,27 @@
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
mod android;
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(any(x11_platform, wayland_platform))]
mod linux;
#[cfg(orbital_platform)]
mod orbital;
#[cfg(web_platform)]
mod web;
#[cfg(windows_platform)]
mod windows;
#[cfg(android_platform)]
use self::android as platform;
#[cfg(target_vendor = "apple")]
use self::apple as platform;
#[path = "windows/mod.rs"]
mod platform;
#[cfg(any(x11_platform, wayland_platform))]
use self::linux as platform;
#[cfg(orbital_platform)]
use self::orbital as platform;
#[path = "linux/mod.rs"]
mod platform;
#[cfg(macos_platform)]
#[path = "macos/mod.rs"]
mod platform;
#[cfg(android_platform)]
#[path = "android/mod.rs"]
mod platform;
#[cfg(ios_platform)]
#[path = "ios/mod.rs"]
mod platform;
#[cfg(web_platform)]
use self::web as platform;
#[cfg(windows_platform)]
use self::windows as platform;
#[path = "web/mod.rs"]
mod platform;
#[cfg(orbital_platform)]
#[path = "orbital/mod.rs"]
mod platform;
pub use self::platform::*;

View File

@@ -272,18 +272,16 @@ impl EventState {
}
}
pub struct EventLoop {
pub struct EventLoop<T> {
windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: event_loop::ActiveEventLoop,
user_events_sender: mpsc::SyncSender<()>,
user_events_receiver: mpsc::Receiver<()>,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: mpsc::Receiver<T>,
}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
// NOTE: Create a channel which can hold only one event to automatically _squash_ user
// events.
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
let (user_events_sender, user_events_receiver) = mpsc::channel();
let event_socket = Arc::new(
RedoxSocket::event()
@@ -325,7 +323,7 @@ impl EventLoop {
})
}
fn process_event<A: ApplicationHandler>(
fn process_event<A: ApplicationHandler<T>>(
window_id: WindowId,
event_option: EventOption,
event_state: &mut EventState,
@@ -502,7 +500,7 @@ impl EventLoop {
}
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);
@@ -596,8 +594,8 @@ impl EventLoop {
i += 1;
}
while self.user_events_receiver.try_recv().is_ok() {
app.proxy_wake_up(&self.window_target);
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(&self.window_target, event);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -685,7 +683,7 @@ impl EventLoop {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy {
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
wake_socket: self.window_target.p.wake_socket.clone(),
@@ -693,22 +691,24 @@ impl EventLoop {
}
}
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
wake_socket: Arc<TimeSocket>,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
self.wake_socket.wake().unwrap();
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
self.wake_socket.wake().unwrap();
Ok(())
}
}
impl Clone for EventLoopProxy {
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
user_events_sender: self.user_events_sender.clone(),
@@ -717,7 +717,7 @@ impl Clone for EventLoopProxy {
}
}
impl Unpin for EventLoopProxy {}
impl<T> Unpin for EventLoopProxy<T> {}
pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>,

View File

@@ -103,7 +103,7 @@ pub struct WindowId {
impl WindowId {
pub const fn dummy() -> Self {
WindowId { fd: u64::MAX }
WindowId { fd: u64::max_value() }
}
}

View File

@@ -1,7 +1,8 @@
use std::future;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, RecvError, SendError, TryRecvError};
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::task::Poll;
use super::AtomicWaker;
@@ -10,35 +11,54 @@ pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let (sender, receiver) = mpsc::channel();
let shared = Arc::new(Shared { closed: AtomicBool::new(false), waker: AtomicWaker::new() });
let sender = Sender { sender, shared: Arc::clone(&shared) };
let receiver = Receiver { receiver, shared };
let sender =
Sender(Arc::new(SenderInner { sender: Mutex::new(sender), shared: Arc::clone(&shared) }));
let receiver = Receiver { receiver: Rc::new(receiver), shared };
(sender, receiver)
}
pub struct Sender<T> {
sender: mpsc::Sender<T>,
pub struct Sender<T>(Arc<SenderInner<T>>);
struct SenderInner<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap `Sender` in an `Arc` to make it clonable on the main thread without
// having to block.
sender: Mutex<mpsc::Sender<T>>,
shared: Arc<Shared>,
}
impl<T> Sender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.sender.send(event)?;
self.shared.waker.wake();
self.0.sender.lock().unwrap().send(event)?;
self.0.shared.waker.wake();
Ok(())
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
impl<T> SenderInner<T> {
fn close(&self) {
self.shared.closed.store(true, Ordering::Relaxed);
self.shared.waker.wake();
}
}
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T> Drop for SenderInner<T> {
fn drop(&mut self) {
self.close();
}
}
pub struct Receiver<T> {
receiver: mpsc::Receiver<T>,
receiver: Rc<mpsc::Receiver<T>>,
shared: Arc<Shared>,
}
@@ -75,6 +95,18 @@ impl<T> Receiver<T> {
}
}
impl<T> Clone for Receiver<T> {
fn clone(&self) -> Self {
Self { receiver: Rc::clone(&self.receiver), shared: Arc::clone(&self.shared) }
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
}
}
struct Shared {
closed: AtomicBool,
waker: AtomicWaker,

View File

@@ -1,10 +1,9 @@
use super::super::main_thread::MainThreadMarker;
use super::{channel, Receiver, Sender, Wrapper};
use std::cell::Ref;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex};
pub struct Dispatcher<T: 'static>(Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>);
pub struct Dispatcher<T: 'static>(Wrapper<T, Sender<Closure<T>>, Closure<T>>);
struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
@@ -12,8 +11,6 @@ impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner<T>)> {
let (sender, receiver) = channel::<Closure<T>>();
let sender = Arc::new(sender);
let receiver = Rc::new(receiver);
Wrapper::new(
main_thread,
@@ -24,7 +21,7 @@ impl<T> Dispatcher<T> {
closure(value.borrow().as_ref().unwrap())
},
{
let receiver = Rc::clone(&receiver);
let receiver = receiver.clone();
move |value| async move {
while let Ok(Closure(closure)) = receiver.next().await {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't
@@ -71,12 +68,7 @@ impl<T> Dispatcher<T> {
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
// safe because this function won't return until `f` has finished executing. See
// `Self::new()`.
let closure = Closure(unsafe {
std::mem::transmute::<
Box<dyn FnOnce(&T) + Send>,
Box<dyn FnOnce(&T) + Send + 'static>,
>(closure)
});
let closure = Closure(unsafe { std::mem::transmute(closure) });
self.0.send(closure);
@@ -92,8 +84,8 @@ impl<T> Dispatcher<T> {
}
pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>,
receiver: Rc<Receiver<Closure<T>>>,
wrapper: Wrapper<T, Sender<Closure<T>>, Closure<T>>,
receiver: Receiver<Closure<T>>,
}
impl<T> DispatchRunner<T> {

View File

@@ -1,18 +1,18 @@
use std::future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Poll;
use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
struct Handler<T> {
value: T,
handler: fn(&T, bool),
handler: fn(&T, usize),
}
#[derive(Clone)]
@@ -20,9 +20,9 @@ struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, bool)) -> Option<Self> {
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
let inner = Arc::new(Inner {
awoken: AtomicBool::new(false),
counter: AtomicUsize::new(0),
waker: AtomicWaker::new(),
closed: AtomicBool::new(false),
});
@@ -34,26 +34,30 @@ impl<T> WakerSpawner<T> {
let wrapper = Wrapper::new(
main_thread,
handler,
|handler, _| {
|handler, count| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, true);
(handler.handler)(&handler.value, count);
},
{
let inner = Arc::clone(&inner);
move |handler| async move {
while future::poll_fn(|cx| {
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
while let Some(count) = future::poll_fn(|cx| {
let count = inner.counter.swap(0, Ordering::Relaxed);
if count > 0 {
Poll::Ready(Some(count))
} else {
inner.waker.register(cx.waker());
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
let count = inner.counter.swap(0, Ordering::Relaxed);
if count > 0 {
Poll::Ready(Some(count))
} else {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(false);
return Poll::Ready(None);
}
Poll::Pending
@@ -64,13 +68,13 @@ impl<T> WakerSpawner<T> {
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, false);
(handler.handler)(&handler.value, count);
}
}
},
sender,
|inner, _| {
inner.0.awoken.store(true, Ordering::Relaxed);
inner.0.counter.fetch_add(1, Ordering::Relaxed);
inner.0.waker.wake();
},
)?;
@@ -82,13 +86,13 @@ impl<T> WakerSpawner<T> {
Waker(self.0.clone())
}
pub fn take(&self) -> bool {
pub fn fetch(&self) -> usize {
debug_assert!(
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);
self.0.with_sender_data(|inner| inner.0.awoken.swap(false, Ordering::Relaxed))
self.0.with_sender_data(|inner| inner.0.counter.swap(0, Ordering::Relaxed))
}
}
@@ -103,7 +107,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(())
self.0.send(1)
}
}
@@ -114,7 +118,7 @@ impl<T> Clone for Waker<T> {
}
struct Inner {
awoken: AtomicBool,
counter: AtomicUsize,
waker: AtomicWaker,
closed: AtomicBool,
}

View File

@@ -324,11 +324,7 @@ impl Inner {
match &self.cursor {
SelectedCursor::Icon(icon)
| SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => {
if let CursorIcon::Default = icon {
self.style.remove("cursor")
} else {
self.style.set("cursor", icon.name())
}
self.style.set("cursor", icon.name())
},
SelectedCursor::Loading { previous: Previous::Image(cursor), .. }
| SelectedCursor::Image(cursor) => {

View File

@@ -1,10 +1,10 @@
use std::marker::PhantomData;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy, WaitUntilStrategy};
use super::{backend, device, window};
@@ -16,31 +16,33 @@ mod window_target;
pub(crate) use proxy::EventLoopProxy;
pub(crate) use window_target::{ActiveEventLoop, OwnedDisplayHandle};
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
elw: RootActiveEventLoop,
user_event_sender: Sender<T>,
user_event_receiver: Receiver<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
impl<T> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let (user_event_sender, user_event_receiver) = mpsc::channel();
let elw = RootActiveEventLoop { p: ActiveEventLoop::new(), _marker: PhantomData };
Ok(EventLoop { elw })
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
}
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
let handler: Box<dyn FnMut(Event)> = Box::new(|event| handle_event(app, &target, event));
let handler: Box<dyn FnMut(Event<()>)> =
Box::new(|event| handle_event(app, &target, &self.user_event_receiver, event));
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
};
let handler = unsafe { std::mem::transmute(handler) };
self.elw.p.run(handler, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
@@ -52,43 +54,41 @@ impl EventLoop {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, mut app: A) {
pub fn spawn_app<A: ApplicationHandler<T> + 'static>(self, mut app: A) {
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
self.elw.p.run(Box::new(move |event| handle_event(&mut app, &target, event)), true);
self.elw.p.run(
Box::new(move |event| {
handle_event(&mut app, &target, &self.user_event_receiver, event)
}),
true,
);
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.elw.p.waker())
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.elw.p.waker(), self.user_event_sender.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.elw
}
pub fn set_poll_strategy(&self, strategy: PollStrategy) {
self.elw.set_poll_strategy(strategy);
}
pub fn poll_strategy(&self) -> PollStrategy {
self.elw.poll_strategy()
}
pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.elw.set_wait_until_strategy(strategy);
}
pub fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.elw.wait_until_strategy()
}
}
fn handle_event<A: ApplicationHandler>(app: &mut A, target: &RootActiveEventLoop, event: Event) {
fn handle_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
target: &RootActiveEventLoop,
user_event_receiver: &Receiver<T>,
event: Event<()>,
) {
match event {
Event::NewEvents(cause) => app.new_events(target, cause),
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
Event::UserWakeUp => app.proxy_wake_up(target),
Event::UserEvent(_) => {
let event =
user_event_receiver.try_recv().expect("user event signaled but not received");
app.user_event(target, event);
},
Event::Suspended => app.suspended(target),
Event::Resumed => app.resumed(target),
Event::AboutToWait => app.about_to_wait(target),

View File

@@ -1,19 +1,29 @@
use std::rc::Weak;
use std::sync::mpsc::{SendError, Sender};
use super::runner::Execution;
use crate::event_loop::EventLoopClosed;
use crate::platform_impl::platform::r#async::Waker;
#[derive(Clone)]
pub struct EventLoopProxy {
pub struct EventLoopProxy<T: 'static> {
runner: Waker<Weak<Execution>>,
sender: Sender<T>,
}
impl EventLoopProxy {
pub fn new(runner: Waker<Weak<Execution>>) -> Self {
Self { runner }
impl<T: 'static> EventLoopProxy<T> {
pub fn new(runner: Waker<Weak<Execution>>, sender: Sender<T>) -> Self {
Self { runner, sender }
}
pub fn wake_up(&self) {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|SendError(event)| EventLoopClosed(event))?;
self.runner.wake();
Ok(())
}
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { runner: self.runner.clone(), sender: self.sender.clone() }
}
}

View File

@@ -8,26 +8,24 @@ use crate::event::{
WindowEvent,
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform::web::PollStrategy;
use crate::platform_impl::platform::backend::EventListenerHandle;
use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner};
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
use js_sys::Function;
use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque};
use std::iter;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::Closure;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
pub struct Shared(Rc<Execution>);
pub(super) type EventHandler = dyn FnMut(Event);
pub(super) type EventHandler = dyn FnMut(Event<()>);
impl Clone for Shared {
fn clone(&self) -> Self {
@@ -42,7 +40,6 @@ pub struct Execution {
proxy_spawner: WakerSpawner<Weak<Self>>,
control_flow: Cell<ControlFlow>,
poll_strategy: Cell<PollStrategy>,
wait_until_strategy: Cell<WaitUntilStrategy>,
exit: Cell<bool>,
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
@@ -136,9 +133,9 @@ impl Shared {
let document = window.document().expect("Failed to obtain document");
Shared(Rc::<Execution>::new_cyclic(|weak| {
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, local| {
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_proxy_wake_up(local);
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
}
})
.expect("`EventLoop` has to be created in the main thread");
@@ -148,7 +145,6 @@ impl Shared {
proxy_spawner,
control_flow: Cell::new(ControlFlow::default()),
poll_strategy: Cell::new(PollStrategy::default()),
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
exit: Cell::new(false),
runner: RefCell::new(RunnerEnum::Pending),
suspended: Cell::new(false),
@@ -201,7 +197,7 @@ impl Shared {
// 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 RootActiveEventLoop reference
pub(crate) fn set_listener(&self, event_handler: Box<EventHandler>) {
pub fn set_listener(&self, event_handler: Box<EventHandler>) {
{
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
@@ -464,46 +460,6 @@ impl Shared {
self.send_events(iter::once(event));
}
// Add a user event to the event loop runner.
//
// This will schedule the event loop to wake up instead of waking it up immediately if its not
// running.
pub(crate) fn send_proxy_wake_up(&self, local: bool) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
if local {
// If the loop is not running and triggered locally, queue on next microtick.
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = queueMicrotask)]
fn queue_microtask(task: Function);
}
queue_microtask(
Closure::once_into_js({
let this = Rc::downgrade(&self.0);
move || {
if let Some(shared) = this.upgrade() {
Shared(shared).send_event(Event::UserWakeUp)
}
}
})
.unchecked_into(),
);
return;
}
}
self.send_event(Event::UserWakeUp);
}
// Add a series of events to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
@@ -515,8 +471,13 @@ impl Shared {
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
// If the runner is attached but not running, we always wake it up.
Ok(RunnerEnum::Running(_)) => (),
Ok(RunnerEnum::Running(ref runner)) => {
// If we're currently polling, queue this and wait for the poll() method to be
// called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
},
Ok(RunnerEnum::Pending) => {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
@@ -644,10 +605,8 @@ impl Shared {
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
events.extend(
self.0
.proxy_spawner
.take()
.then_some(Event::UserWakeUp)
iter::repeat(Event::UserEvent(()))
.take(self.0.proxy_spawner.fetch())
.map(EventWrapper::from),
);
@@ -688,7 +647,6 @@ impl Shared {
start,
end,
_timeout: backend::Schedule::new_with_duration(
self.wait_until_strategy(),
self.window(),
move || cloned.resume_time_reached(start, end),
delay,
@@ -735,7 +693,7 @@ impl Shared {
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
if self.0.event_loop_recreation.get() {
crate::event_loop::EventLoopBuilder::allow_event_loop_recreation();
crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation();
}
}
@@ -801,26 +759,18 @@ impl Shared {
self.0.poll_strategy.get()
}
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.0.wait_until_strategy.set(strategy)
}
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.0.wait_until_strategy.get()
}
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
self.0.proxy_spawner.waker()
}
}
pub(crate) enum EventWrapper {
Event(Event),
Event(Event<()>),
ScaleChange { canvas: Weak<RefCell<backend::Canvas>>, size: PhysicalSize<u32>, scale: f64 },
}
impl From<Event> for EventWrapper {
fn from(value: Event) -> Self {
impl From<Event<()>> for EventWrapper {
fn from(value: Event<()>) -> Self {
Self::Event(value)
}
}

View File

@@ -18,7 +18,7 @@ use crate::event::{
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::keyboard::ModifiersState;
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
use crate::platform::web::{CustomCursorFuture, PollStrategy};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::platform_impl::platform::r#async::Waker;
use crate::window::{
@@ -55,11 +55,7 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(
&self,
event_handler: Box<runner::EventHandler>,
event_loop_recreation: bool,
) {
pub fn run(&self, event_handler: Box<runner::EventHandler>, event_loop_recreation: bool) {
self.runner.event_loop_recreation(event_loop_recreation);
self.runner.set_listener(event_handler);
}
@@ -686,14 +682,6 @@ impl ActiveEventLoop {
self.runner.poll_strategy()
}
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.runner.set_wait_until_strategy(strategy)
}
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.runner.wait_until_strategy()
}
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
self.runner.waker()
}

View File

@@ -28,9 +28,11 @@ mod event_loop;
mod keyboard;
mod main_thread;
mod monitor;
mod web_sys;
mod window;
#[path = "web_sys/mod.rs"]
mod backend;
pub use self::device::DeviceId;
pub use self::error::OsError;
pub(crate) use self::event_loop::{
@@ -41,7 +43,6 @@ pub use self::monitor::{MonitorHandle, VideoModeHandle};
pub use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
pub(crate) use self::keyboard::KeyEventExtra;
use self::web_sys as backend;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) use cursor::{

View File

@@ -427,8 +427,8 @@ impl Canvas {
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
self.on_resize_scale = Some(ResizeScaleHandle::new(
self.window().clone(),
@@ -483,7 +483,7 @@ impl Canvas {
pub(crate) fn handle_scale_change(
&self,
runner: &super::super::event_loop::runner::Shared,
event_handler: impl FnOnce(crate::event::Event),
event_handler: impl FnOnce(crate::event::Event<()>),
current_size: PhysicalSize<u32>,
scale: f64,
) {

View File

@@ -16,7 +16,7 @@ use super::media_query_handle::MediaQueryListHandle;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub struct ResizeScaleHandle(Rc<ResizeScaleInternal>);
pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>);
impl ResizeScaleHandle {
pub(crate) fn new<S, R>(
@@ -28,8 +28,8 @@ impl ResizeScaleHandle {
resize_handler: R,
) -> Self
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
Self(ResizeScaleInternal::new(
window,
@@ -42,7 +42,7 @@ impl ResizeScaleHandle {
}
pub(crate) fn notify_resize(&self) {
self.0.notify()
self.0.borrow_mut().notify()
}
}
@@ -53,11 +53,11 @@ struct ResizeScaleInternal {
document: Document,
canvas: HtmlCanvasElement,
style: Style,
mql: RefCell<MediaQueryListHandle>,
mql: MediaQueryListHandle,
observer: ResizeObserver,
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
scale_handler: Box<dyn Fn(PhysicalSize<u32>, f64)>,
resize_handler: Box<dyn Fn(PhysicalSize<u32>)>,
scale_handler: Box<dyn FnMut(PhysicalSize<u32>, f64)>,
resize_handler: Box<dyn FnMut(PhysicalSize<u32>)>,
notify_scale: Cell<bool>,
}
@@ -69,12 +69,12 @@ impl ResizeScaleInternal {
style: Style,
scale_handler: S,
resize_handler: R,
) -> Rc<Self>
) -> Rc<RefCell<Self>>
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
Rc::<ResizeScaleInternal>::new_cyclic(|weak_self| {
Rc::<RefCell<ResizeScaleInternal>>::new_cyclic(|weak_self| {
let mql = Self::create_mql(&window, {
let weak_self = weak_self.clone();
move |mql| {
@@ -86,7 +86,9 @@ impl ResizeScaleInternal {
let weak_self = weak_self.clone();
let observer_closure = Closure::new(move |entries: Array, _| {
if let Some(this) = weak_self.upgrade() {
if let Some(rc_self) = weak_self.upgrade() {
let mut this = rc_self.borrow_mut();
let size = this.process_entry(entries);
if this.notify_scale.replace(false) {
@@ -99,18 +101,18 @@ impl ResizeScaleInternal {
});
let observer = Self::create_observer(&canvas, observer_closure.as_ref());
Self {
RefCell::new(Self {
window,
document,
canvas,
style,
mql: RefCell::new(mql),
mql,
observer,
_observer_closure: observer_closure,
scale_handler: Box::new(scale_handler),
resize_handler: Box::new(resize_handler),
notify_scale: Cell::new(false),
}
})
})
}
@@ -150,7 +152,7 @@ impl ResizeScaleInternal {
observer
}
fn notify(&self) {
fn notify(&mut self) {
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
let size = PhysicalSize::new(0, 0);
@@ -198,9 +200,10 @@ impl ResizeScaleInternal {
}
}
fn handle_scale(self: Rc<Self>, mql: &MediaQueryList) {
let weak_self = Rc::downgrade(&self);
let scale = super::scale_factor(&self.window);
fn handle_scale(this: Rc<RefCell<Self>>, mql: &MediaQueryList) {
let weak_self = Rc::downgrade(&this);
let mut this = this.borrow_mut();
let scale = super::scale_factor(&this.window);
// TODO: confirm/reproduce this problem, see:
// <https://github.com/rust-windowing/winit/issues/2597>.
@@ -214,15 +217,15 @@ impl ResizeScaleInternal {
return;
}
let new_mql = Self::create_mql(&self.window, move |mql| {
let new_mql = Self::create_mql(&this.window, move |mql| {
if let Some(rc_self) = weak_self.upgrade() {
Self::handle_scale(rc_self, mql);
}
});
self.mql.replace(new_mql);
this.mql = new_mql;
self.notify_scale.set(true);
self.notify();
this.notify_scale.set(true);
this.notify();
}
fn process_entry(&self, entries: Array) -> PhysicalSize<u32> {

View File

@@ -1,14 +1,12 @@
use js_sys::{Array, Function, Object, Promise, Reflect};
use js_sys::{Function, Object, Promise, Reflect};
use std::cell::OnceCell;
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{
AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker,
};
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform::web::PollStrategy;
#[derive(Debug)]
pub struct Schedule {
@@ -31,7 +29,6 @@ enum Inner {
port: MessagePort,
_timeout_closure: Closure<dyn FnMut()>,
},
Worker(MessagePort),
}
impl Schedule {
@@ -48,24 +45,14 @@ impl Schedule {
}
}
pub fn new_with_duration<F>(
strategy: WaitUntilStrategy,
window: &web_sys::Window,
f: F,
duration: Duration,
) -> Schedule
pub fn new_with_duration<F>(window: &web_sys::Window, f: F, duration: Duration) -> Schedule
where
F: 'static + FnMut(),
{
match strategy {
WaitUntilStrategy::Scheduler => {
if has_scheduler_support(window) {
Self::new_scheduler(window, f, Some(duration))
} else {
Self::new_timeout(window.clone(), f, Some(duration))
}
},
WaitUntilStrategy::Worker => Self::new_worker(f, duration),
if has_scheduler_support(window) {
Self::new_scheduler(window, f, Some(duration))
} else {
Self::new_timeout(window.clone(), f, Some(duration))
}
}
@@ -87,7 +74,7 @@ impl Schedule {
let duration = duration
.as_secs()
.checked_mul(1000)
.and_then(|secs| secs.checked_add(duration.subsec_micros().div_ceil(1000).into()))
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration).into()))
.unwrap_or(u64::MAX);
options.delay(duration as f64);
@@ -140,9 +127,7 @@ impl Schedule {
.ok()
.and_then(|secs: i32| secs.checked_mul(1000))
.and_then(|secs: i32| {
let millis: i32 = duration
.subsec_micros()
.div_ceil(1000)
let millis: i32 = duration_millis_ceil(duration)
.try_into()
.expect("millis are somehow bigger then 1K");
secs.checked_add(millis)
@@ -168,44 +153,6 @@ impl Schedule {
},
}
}
fn new_worker<F>(f: F, duration: Duration) -> Schedule
where
F: 'static + FnMut(),
{
thread_local! {
static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js"));
static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script");
}
let channel = MessageChannel::new().unwrap();
let closure = Closure::new(f);
let port_1 = channel.port1();
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
port_1.start();
// `Duration::as_millis()` always rounds down (because of truncation), we want to round
// up instead. This makes sure that the we never wake up **before** the given time.
let duration = duration
.as_secs()
.try_into()
.ok()
.and_then(|secs: u32| secs.checked_mul(1000))
.and_then(|secs| secs.checked_add(duration.subsec_micros().div_ceil(1000)))
.unwrap_or(u32::MAX);
WORKER
.with(|worker| {
let port_2 = channel.port2();
worker.post_message_with_transfer(
&Array::of2(&port_2, &duration.into()),
&Array::of1(&port_2).into(),
)
})
.expect("`Worker.postMessage()` is not expected to fail");
Schedule { _closure: closure, inner: Inner::Worker(port_1) }
}
}
impl Drop for Schedule {
@@ -218,14 +165,24 @@ impl Drop for Schedule {
port.close();
port.set_onmessage(None);
},
Inner::Worker(port) => {
port.close();
port.set_onmessage(None);
},
}
}
}
// TODO: Replace with `u32::div_ceil()` when we hit Rust v1.73.
fn duration_millis_ceil(duration: Duration) -> u32 {
let micros = duration.subsec_micros();
// From <https://doc.rust-lang.org/1.73.0/src/core/num/uint_macros.rs.html#2086-2094>.
let d = micros / 1000;
let r = micros % 1000;
if r > 0 && 1000 > 0 {
d + 1
} else {
d
}
}
fn has_scheduler_support(window: &web_sys::Window) -> bool {
thread_local! {
static SCHEDULER_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
@@ -269,29 +226,6 @@ fn has_idle_callback_support(window: &web_sys::Window) -> bool {
})
}
struct ScriptUrl(String);
impl ScriptUrl {
fn new(script: &str) -> Self {
let sequence = Array::of1(&script.into());
let mut property = BlobPropertyBag::new();
property.type_("text/javascript");
let blob = Blob::new_with_str_sequence_and_options(&sequence, &property)
.expect("`new Blob()` should never throw");
let url = Url::create_object_url_with_blob(&blob)
.expect("`URL.createObjectURL()` should never throw");
Self(url)
}
}
impl Drop for ScriptUrl {
fn drop(&mut self) {
Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw");
}
}
#[wasm_bindgen]
extern "C" {
type WindowSupportExt;

View File

@@ -1,10 +0,0 @@
onmessage = event => {
const [port, timeout] = event.data
const f = () => port.postMessage(undefined)
if ('scheduler' in this) {
scheduler.postTask(f, { delay: timeout })
} else {
setTimeout(f, timeout)
}
}

View File

@@ -1 +0,0 @@
onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)};

View File

@@ -25,7 +25,7 @@ pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
send_event: Box<dyn Fn(Event)>,
send_event: Box<dyn Fn(Event<()>)>,
cursor_effect: u32,
hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
* `HoveredFileCancelled` emitted */
@@ -37,7 +37,7 @@ pub struct FileDropHandler {
#[allow(non_snake_case)]
impl FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(Event)>) -> FileDropHandler {
pub fn new(window: HWND, send_event: Box<dyn Fn(Event<()>)>) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl },
refcount: AtomicUsize::new(1),
@@ -211,7 +211,7 @@ impl FileDropHandler {
}
impl FileDropHandlerData {
fn send_event(&self, event: Event) {
fn send_event(&self, event: Event<()>) {
(self.send_event)(event);
}
}

View File

@@ -8,6 +8,7 @@ use std::ffi::c_void;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant};
use std::{mem, panic, ptr};
@@ -62,7 +63,7 @@ use crate::error::EventLoopError;
use crate::event::{
DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, WindowEvent,
};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::keyboard::ModifiersState;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::dark_mode::try_theme;
@@ -83,14 +84,35 @@ use crate::platform_impl::platform::{
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
};
use runner::EventLoopRunner;
use runner::{EventLoopRunner, EventLoopRunnerShared};
use super::window::set_skip_taskbar;
use super::SelectedCursor;
/// some backends like macos uses an uninhabited `Never` type,
/// on windows, `UserEvent`s are also dispatched through the
/// WNDPROC callback, and due to the re-entrant nature of the
/// callback, recursively delivered events must be queued in a
/// buffer, the current implementation put this queue in
/// `EventLoopRunner`, which is shared between the event pumping
/// loop and the callback. because it's hard to decide from the
/// outside whether a event needs to be buffered, I decided not
/// use `Event<Never>` for the shared runner state, but use unit
/// as a placeholder so user events can be buffered as usual,
/// the real `UserEvent` is pulled from the mpsc channel directly
/// when the placeholder event is delivered to the event handler
pub(crate) struct UserEventPlaceholder;
// here below, the generic `EventLoopRunnerShared<T>` is replaced with
// `EventLoopRunnerShared<UserEventPlaceholder>` so we can get rid
// of the generic parameter T in types which don't depend on T.
// this is the approach which requires minimum changes to current
// backend implementation. it should be considered transitional
// and should be refactored and cleaned up eventually, I hope.
pub(crate) struct WindowData {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: Rc<EventLoopRunner>,
pub event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
pub key_event_builder: KeyEventBuilder,
pub _file_drop_handler: Option<FileDropHandler>,
pub userdata_removed: Cell<bool>,
@@ -98,7 +120,7 @@ pub(crate) struct WindowData {
}
impl WindowData {
fn send_event(&self, event: Event) {
fn send_event(&self, event: Event<UserEventPlaceholder>) {
self.event_loop_runner.send_event(event);
}
@@ -108,11 +130,11 @@ impl WindowData {
}
struct ThreadMsgTargetData {
event_loop_runner: Rc<EventLoopRunner>,
event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
}
impl ThreadMsgTargetData {
fn send_event(&self, event: Event) {
fn send_event(&self, event: Event<UserEventPlaceholder>) {
self.event_loop_runner.send_event(event);
}
}
@@ -124,7 +146,9 @@ pub(crate) enum ProcResult {
Value(isize),
}
pub struct EventLoop {
pub struct EventLoop<T: 'static> {
user_event_sender: Sender<T>,
user_event_receiver: Receiver<T>,
window_target: RootAEL,
msg_hook: Option<Box<dyn FnMut(*const c_void) -> bool + 'static>>,
}
@@ -144,10 +168,10 @@ impl Default for PlatformSpecificEventLoopAttributes {
pub struct ActiveEventLoop {
thread_id: u32,
thread_msg_target: HWND,
pub(crate) runner_shared: Rc<EventLoopRunner>,
pub(crate) runner_shared: EventLoopRunnerShared<UserEventPlaceholder>,
}
impl EventLoop {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(
attributes: &mut PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -170,6 +194,7 @@ impl EventLoop {
let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target));
let (user_event_sender, user_event_receiver) = mpsc::channel();
insert_event_target_window_data(thread_msg_target, runner_shared.clone());
raw_input::register_all_mice_and_keyboards_for_raw_input(
thread_msg_target,
@@ -177,6 +202,8 @@ impl EventLoop {
);
Ok(EventLoop {
user_event_sender,
user_event_receiver,
window_target: RootAEL {
p: ActiveEventLoop { thread_id, thread_msg_target, runner_shared },
_marker: PhantomData,
@@ -189,11 +216,11 @@ impl EventLoop {
&self.window_target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
@@ -201,24 +228,38 @@ impl EventLoop {
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
let user_event_receiver = &self.user_event_receiver;
// # Safety
// We make sure to call runner.clear_event_handler() before
// returning
unsafe {
runner.set_event_handler(move |event| match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
Event::UserWakeUp => app.proxy_wake_up(event_loop_windows_ref),
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
runner.set_event_handler(move |event| {
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
.try_recv()
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
});
}
}
@@ -252,7 +293,7 @@ impl EventLoop {
}
}
pub fn pump_app_events<A: ApplicationHandler>(
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
@@ -260,7 +301,7 @@ impl EventLoop {
{
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
// let user_event_receiver = &self.user_event_receiver;
let user_event_receiver = &self.user_event_receiver;
// # Safety
// We make sure to call runner.clear_event_handler() before
@@ -270,20 +311,33 @@ impl EventLoop {
// to leave the runner in an unsound state with an associated
// event handler.
unsafe {
runner.set_event_handler(move |event| match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
Event::UserWakeUp => app.proxy_wake_up(event_loop_windows_ref),
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
runner.set_event_handler(move |event| {
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
.try_recv()
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
});
runner.wakeup();
@@ -468,8 +522,11 @@ impl EventLoop {
}
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { target_window: self.window_target.p.thread_msg_target }
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
target_window: self.window_target.p.thread_msg_target,
event_send: self.user_event_sender.clone(),
}
}
fn exit_code(&self) -> Option<i32> {
@@ -641,7 +698,7 @@ fn dur2timeout(dur: Duration) -> u32 {
.unwrap_or(INFINITE)
}
impl Drop for EventLoop {
impl<T> Drop for EventLoop<T> {
fn drop(&mut self) {
unsafe {
DestroyWindow(self.window_target.p.thread_msg_target);
@@ -699,16 +756,27 @@ impl EventLoopThreadExecutor {
type ThreadExecFn = Box<Box<dyn FnMut()>>;
#[derive(Clone)]
pub struct EventLoopProxy {
pub struct EventLoopProxy<T: 'static> {
target_window: HWND,
event_send: Sender<T>,
}
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { target_window: self.target_window, event_send: self.event_send.clone() }
}
}
unsafe impl Send for EventLoopProxy {}
impl EventLoopProxy {
pub fn wake_up(&self) {
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_send
.send(event)
.map(|result| {
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
result
})
.map_err(|e| EventLoopClosed(e.0))
}
}
@@ -840,7 +908,7 @@ fn create_event_target_window() -> HWND {
fn insert_event_target_window_data(
thread_msg_target: HWND,
event_loop_runner: Rc<EventLoopRunner>,
event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
) {
let userdata = ThreadMsgTargetData { event_loop_runner };
let input_ptr = Box::into_raw(Box::new(userdata));
@@ -2386,7 +2454,7 @@ unsafe extern "system" fn thread_event_target_callback(
// user event is still in the mpsc channel and will be pulled
// once the placeholder event is delivered to the wrapper
// `event_handler`
userdata.send_event(Event::UserWakeUp);
userdata.send_event(Event::UserEvent(UserEventPlaceholder));
0
},
_ if msg == EXEC_MSG_ID.get() => {

View File

@@ -1,6 +1,7 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{mem, panic};
@@ -15,9 +16,11 @@ use crate::window::WindowId;
use super::ControlFlow;
type EventHandler = Cell<Option<Box<dyn FnMut(Event)>>>;
pub(crate) type EventLoopRunnerShared<T> = Rc<EventLoopRunner<T>>;
pub(crate) struct EventLoopRunner {
type EventHandler<T> = Cell<Option<Box<dyn FnMut(Event<T>)>>>;
pub(crate) struct EventLoopRunner<T: 'static> {
// The event loop's win32 handles
pub(super) thread_msg_target: HWND,
@@ -30,8 +33,8 @@ pub(crate) struct EventLoopRunner {
exit: Cell<Option<i32>>,
runner_state: Cell<RunnerState>,
last_events_cleared: Cell<Instant>,
event_handler: EventHandler,
event_buffer: RefCell<VecDeque<BufferedEvent>>,
event_handler: EventHandler<T>,
event_buffer: RefCell<VecDeque<BufferedEvent<T>>>,
panic_error: Cell<Option<PanicError>>,
}
@@ -52,13 +55,13 @@ pub(crate) enum RunnerState {
Destroyed,
}
enum BufferedEvent {
Event(Event),
enum BufferedEvent<T: 'static> {
Event(Event<T>),
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
}
impl EventLoopRunner {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner {
impl<T> EventLoopRunner<T> {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner<T> {
EventLoopRunner {
thread_msg_target,
interrupt_msg_dispatch: Cell::new(false),
@@ -85,12 +88,13 @@ impl EventLoopRunner {
/// undefined behaviour.
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
where
F: FnMut(Event),
F: FnMut(Event<T>),
{
// Erase closure lifetime.
// SAFETY: Caller upholds that the lifetime of the closure is upheld.
let f =
unsafe { mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event)>>(Box::new(f)) };
let f = unsafe {
mem::transmute::<Box<dyn FnMut(Event<T>)>, Box<dyn FnMut(Event<T>)>>(Box::new(f))
};
let old_event_handler = self.event_handler.replace(Some(f));
assert!(old_event_handler.is_none());
}
@@ -120,7 +124,7 @@ impl EventLoopRunner {
}
/// State retrieval functions.
impl EventLoopRunner {
impl<T> EventLoopRunner<T> {
#[allow(unused)]
pub fn thread_msg_target(&self) -> HWND {
self.thread_msg_target
@@ -162,7 +166,7 @@ impl EventLoopRunner {
}
/// Misc. functions
impl EventLoopRunner {
impl<T> EventLoopRunner<T> {
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
let panic_error = self.panic_error.take();
if panic_error.is_none() {
@@ -192,7 +196,7 @@ impl EventLoopRunner {
}
/// Event dispatch functions.
impl EventLoopRunner {
impl<T> EventLoopRunner<T> {
pub(crate) fn prepare_wait(&self) {
self.move_state_to(RunnerState::Idle);
}
@@ -201,7 +205,7 @@ impl EventLoopRunner {
self.move_state_to(RunnerState::HandlingMainEvents);
}
pub(crate) fn send_event(&self, event: Event) {
pub(crate) fn send_event(&self, event: Event<T>) {
if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event {
self.call_event_handler(event);
// As a rule, to ensure that `pump_events` can't block an external event loop
@@ -222,7 +226,7 @@ impl EventLoopRunner {
self.move_state_to(RunnerState::Destroyed);
}
fn call_event_handler(&self, event: Event) {
fn call_event_handler(&self, event: Event<T>) {
self.catch_unwind(|| {
let mut event_handler = self.event_handler.take().expect(
"either event handler is re-entrant (likely), or no event handler is registered \
@@ -354,8 +358,8 @@ impl EventLoopRunner {
}
}
impl BufferedEvent {
pub fn from_event(event: Event) -> BufferedEvent {
impl<T> BufferedEvent<T> {
pub fn from_event(event: Event<T>) -> BufferedEvent<T> {
match event {
Event::WindowEvent {
event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer },
@@ -369,7 +373,7 @@ impl BufferedEvent {
}
}
pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) {
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<T>)) {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => {

View File

@@ -1,3 +1,5 @@
#![cfg(windows_platform)]
use smol_str::SmolStr;
use windows_sys::Win32::Foundation::{HANDLE, HWND};
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};

View File

@@ -1186,7 +1186,11 @@ impl<'a> InitData<'a> {
let file_drop_runner = self.event_loop.runner_shared.clone();
let file_drop_handler = FileDropHandler::new(
win.window,
Box::new(move |event| file_drop_runner.send_event(event)),
Box::new(move |event| {
if let Ok(e) = event.map_nonuser_event() {
file_drop_runner.send_event(e)
}
}),
);
let handler_interface_ptr =

View File

@@ -393,6 +393,7 @@ impl WindowAttributes {
///
/// ## Platform-specific
///
/// - **macOS:** This is an app-wide setting.
/// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the
/// system preference. When explicit theme is used, this will avoid dbus all together.
/// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`.
@@ -631,8 +632,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **Android / iOS / X11 / Web / Windows / macOS / Orbital:** Unsupported.
/// - **Wayland:** Schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
/// **Wayland:** - schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
///
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
#[inline]
@@ -941,7 +941,8 @@ impl Window {
///
/// ## Platform-specific
///
/// - **macOS:** This will reset the window's background color.
/// - **macOS:** If you're not drawing to the window yourself, you might have to set the
/// background color of the window to enable transparency.
/// - **Web / iOS / Android:** Unsupported.
/// - **X11:** Can only be set while building the window, with
/// [`WindowAttributes::with_transparent`].
@@ -1353,12 +1354,11 @@ impl Window {
self.window.maybe_queue_on_main(move |w| w.request_user_attention(request_type))
}
/// Set or override the window theme.
///
/// Specify `None` to reset the theme to the system default.
/// Sets the current window theme. Use `None` to fallback to system default.
///
/// ## Platform-specific
///
/// - **macOS:** This is an app-wide setting.
/// - **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus to
/// get the system preference.
/// - **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it
@@ -1374,14 +1374,12 @@ impl Window {
self.window.maybe_queue_on_main(move |w| w.set_theme(theme))
}
/// Returns the current window theme override.
///
/// Returns `None` if the current theme is set as the system default, or if it cannot be
/// determined on the current platform.
/// Returns the current window theme.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported, returns `None`.
/// - **macOS:** This is an app-wide setting.
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
#[inline]
pub fn theme(&self) -> Option<Theme> {
let _span = tracing::debug_span!("winit::Window::theme",).entered();

View File

@@ -3,11 +3,16 @@ fn needs_send<T: Send>() {}
#[test]
fn event_loop_proxy_send() {
needs_send::<winit::event_loop::EventLoopProxy>();
#[allow(dead_code)]
fn is_send<T: 'static + Send>() {
// ensures that `winit::EventLoopProxy<T: Send>` implements `Send`
needs_send::<winit::event_loop::EventLoopProxy<T>>();
}
}
#[test]
fn window_send() {
// ensures that `winit::Window` implements `Send`
needs_send::<winit::window::Window>();
}
@@ -18,6 +23,7 @@ fn window_builder_send() {
#[test]
fn ids_send() {
// ensures that the various `..Id` types implement `Send`
needs_send::<winit::window::WindowId>();
needs_send::<winit::event::DeviceId>();
needs_send::<winit::monitor::MonitorHandle>();

View File

@@ -3,11 +3,16 @@ fn needs_sync<T: Sync>() {}
#[test]
fn event_loop_proxy_send() {
needs_sync::<winit::event_loop::EventLoopProxy>();
#[allow(dead_code)]
fn is_send<T: 'static + Send>() {
// ensures that `winit::EventLoopProxy<T: Send>` implements `Sync`
needs_sync::<winit::event_loop::EventLoopProxy<T>>();
}
}
#[test]
fn window_sync() {
// ensures that `winit::Window` implements `Sync`
needs_sync::<winit::window::Window>();
}