Compare commits

..

58 Commits

Author SHA1 Message Date
Kirill Chibisov
114512bb32 Winit version 0.30.5 2024-08-08 21:26:00 +03:00
Kirill Chibisov
8c07319ced chore: drop libera link
It has low amount of users and not active, so better direct people
straight to matrix at this point.
2024-08-08 21:26:00 +03:00
daxpedda
fa3ec006e2 Add ActiveEventLoop::system_theme()
This also fixes macOS returning `None` in `Window::theme()` if no theme
override is set, instead it now returns the system theme.

MacOS and Wayland were the only ones working correctly according to the
documentation, which was an oversight. The documentation was "fixed"
now.

Fixes #3837.
2024-08-08 21:26:00 +03:00
Pascal Hertleif
f3d1f922f9 macOS: skip releasing unavailable monitors
Prevent assertion error when trying to close a fullscreen window that
was on a display that got disconnected.
2024-08-08 21:26:00 +03:00
daxpedda
77e8fe6094 Windows: use Box::leak() instead of Box::into_raw()
This was detected by a new change in Nightly Rust that applied
`#[must_use]` to the return value of `Box::into_raw()`.
2024-08-08 21:26:00 +03:00
Kirill Chibisov
5fce4a09de android: add {Active,}EventLoopExtAndroid::android_app
This type comes from the user and stored for the entire lifetime, so
no need to hide it from them after they've passed it to winit.

Fixes #3818.
2024-08-08 21:26:00 +03:00
daxpedda
5735786f42 Fix CI for Rust v1.80 (#3822)
`clippy::doc_lazy_continuation` was added, which needed some fixing from our side.
2024-08-08 21:26:00 +03:00
daxpedda
9ca8c09208 Web: don't block pen input (#3813) 2024-08-08 21:26:00 +03:00
daxpedda
4b1aa51094 macOS: fix building with feature = "rwh_04" 2024-08-08 21:26:00 +03:00
daxpedda
1dec9b4b33 Web: implement Error for CustomCursorError (#3793) 2024-08-08 21:26:00 +03:00
Kirill Chibisov
04d8a284a0 Winit version 0.30.4 2024-07-16 21:04:25 +03:00
daxpedda
949cb0f203 Web: fix MouseMotion coordinate space (#3770) 2024-07-16 21:04:25 +03:00
daxpedda
bf68ac0b14 Web: fix WindowEvent::Resized not using rAF (#3790) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ba188376d1 wayland: bump dependencies
Update SCTK as a follow-up to 1170554dbd (ignore events to dead
objects) fixing it for wl_output.
2024-07-16 21:04:25 +03:00
daxpedda
6509f8a18b Make DeviceId/WindowId::dummy() safe (#3784) 2024-07-16 21:04:25 +03:00
daxpedda
71dea4637d Fix CI (#3775) 2024-07-16 21:04:25 +03:00
Kirill Chibisov
ec24c3efd3 wayland: ignore events for dead objects
Nothing wrong will happen if we ignore events when compositor is at
wrong, at least crashing because compositor is just _wrong_ probably is
not a great option.

Links: https://github.com/alacritty/alacritty/issues/8065
2024-07-16 21:04:25 +03:00
Mads Marquart
feca480b4c Avoid path when importing modules (#3755)
Rust tooling generally works better this way. This includes
rust-analyzer, but more noticeably the output from `tracing` typically
prints the module path, which did not correspond to the actual file
system before.

Concretely, tracing output from the macOS backend changes from printing:
`winit::platform_impl::platform::util`
To printing:
`winit::platform_impl::macos::util`
2024-07-16 21:04:25 +03:00
John Nunley
655fdc896f ci: Use taiki-e/checkout-action
taiki-e/checkout-action has a few advantages over actions/checkout,
such as:

- It is written in Bash rather than Node.js
- It does not have frequent breaking changes, reducing churn

Signed-off-by: John Nunley <dev@notgull.net>
2024-07-16 21:04:25 +03:00
msiglreith
faa641e57f Add notgull as Windows maintainer 2024-07-16 21:04:25 +03:00
msiglreith
7c81364e1c Update codeowner list 2024-07-16 21:04:25 +03:00
Bruce Mitchener
6abfef1220 Use default-features, not default_features (#3746)
The latter syntax is deprecated and will be removed in Rust
2024 edition. This also generates a warning with current
versions of Rust.
2024-07-16 21:04:25 +03:00
Kirill Chibisov
d2d4d20108 Winit version 0.30.3 2024-06-21 20:16:14 +03:00
daxpedda
d8f4d8f1b7 Web: implement WaitUntilStrategy (#3739) 2024-06-21 20:16:14 +03:00
daxpedda
a974640a66 Web: set control flow strategies on EventLoop (#3740) 2024-06-21 20:16:14 +03:00
Mads Marquart
3d7d766182 macOS: set the theme on the NSWindow, instead of application-wide
This new implementation uses:
- The NSAppearanceCustomization protocol for retrieving the appearance
  of the window, instead of using the application-wide
  `-[NSApplication effectiveAppearance]`.
- Key-Value observing for observing the `effectiveAppearance` to compute
  the `ThemeChanged` event, instead of using the undocumented
  `AppleInterfaceThemeChangedNotification` notification.

This also fixes `WindowBuilder::with_theme` not having any effect, and
the conversion between `Theme` and `NSAppearance` is made a bit more
robust.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
c73d8cff20 x11: fix build on arm
The c_char type, which was used under the hood is different depending
on arch, thus use it directly instead of i8.

Fixes #3735.
2024-06-21 20:16:14 +03:00
Kirill Chibisov
79aa95b212 Winit version 0.30.2 2024-06-15 20:00:04 +03:00
Kirill Chibisov
ecd14688dc Revert: Web: don't wait for polling when sending events
This is a breaking change, thus revert it for patch series.
2024-06-15 20:00:04 +03:00
Kirill Chibisov
b512ed1e63 macOS: fix opacity handling
Not using `NSColor::clearColor()` results in Quartz thinking that the
window is not transparent at all, which results in artifacts.

However, not setting the `windowBackgroundColor` in
`Window::set_transparent` results in border not properly rendered.

Fixes: 94664ff687 (Don't set the background color)
2024-06-15 20:00:04 +03:00
Kirill Chibisov
96388f4f6b chore: address 1.79 clippy lints 2024-06-15 20:00:04 +03:00
daxpedda
1745b01502 Web: fix crash InnerSizeWriter::request_inner_size() (#3727) 2024-06-15 20:00:04 +03:00
daxpedda
54e974c090 Web: don't overwrite cursor with CursorIcon::Default (#3729) 2024-06-15 20:00:04 +03:00
daxpedda
b14d5c0c99 Web: queue EventLoopProxy::send_event() to microtask 2024-06-15 20:00:04 +03:00
John Nunley
437747b966 Winit version 0.30.1
Signed-off-by: John Nunley <dev@notgull.net>
2024-06-10 18:40:33 +03:00
Mads Marquart
21e266f3b7 macOS/iOS: Various refactorings in application state (#3720)
I'm preparing to get rid of our application delegate in favour of registering
notification observers, to do so I'm renaming `app_delegate.rs` to
`app_state.rs`, and moving the functionality out of the Objective-C method
into a normal method.

Additionally, `AppState` previously implemented `Default`, but really, this
was a hack done because someone (probably myself) was too lazy to write out
the full initialization in `AppDelegate::new`.
2024-06-10 18:40:33 +03:00
Mads Marquart
3a0928af45 macOS: Improve event queuing (#3708)
* Use AppKit's internal queuing mechanisms

This allows events to be queued in a more consistent order, they're now
interleaved with events that we handle immediately (like redraw events),
instead of being handled afterwards.

* Only queue events if necessary

This makes the call stack / backtraces easier to understand whenever
possible, and generally improves upon the order in which events are
delivered.
2024-06-10 18:40:33 +03:00
Philippe Renon
ad92b4f89d doc: clarify Window::pre_present_notify availability
Fixes #3703.
2024-06-10 18:40:33 +03:00
ShikiSuen
bf4445bb62 Handle _selected_range sent to NSTextInputClient.setMarkedText(). (#3619)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-06-10 18:40:33 +03:00
Mads Marquart
391a22217d Implement ApplicationHandler for &mut A and Box<A> (#3709) 2024-06-10 18:40:33 +03:00
Mads Marquart
fb4a674ee5 Update objc2 to v0.2.2 (#3702)
- Use new `bitflags!` support.
- Use `objc2-ui-kit`.
- Change usage of `Id` to `Retained`.
2024-06-10 18:40:33 +03:00
Diggory Hardy
43f296b2b3 event_loop: add is_x11 and is_wayland on EventLoop 2024-06-10 18:40:33 +03:00
Golden Water
042667c5eb Resize when size changes on scale change on macOS
This fixes an issue where the window glitched due to resize
when the user doesn't actually change the window, but macOS
function to update window size was still called.
2024-06-10 18:40:33 +03:00
Kirill Chibisov
3206d105fe chore: explicitly use cfg_aliases 0.2.1
This correctly handles recent nightly lint that requires to explicitly
define the CFG guards.
2024-06-10 18:40:33 +03:00
Kevin Müller
1afec3ca0d bugfix: Replace pointer dereference with read_unaligned
On Raspberry Pi, using the Rust crate eframe caused the program to crash on
mouse movement. The Backtrace lead to this specific line of code, and the exact
error was a "misaligned pointer dereference: address must be a multiple of 0x8
but is xxxx"

The edit has been tested with the Raspberry Pi, which works now.
2024-06-10 18:40:33 +03:00
Ryan Burleson
c4a8e9321d fix doc typo in application.rs (#3676) 2024-06-10 18:40:33 +03:00
linkmauve
dee7a405fc Reexport older versions of raw-window-handle
When the user decides to use an older version of raw-window-handle,
through the rwh_04 or rwh_05 features, it makes sense to reexport the
crate so they don’t have to depend on it manually and can instead use
winit::raw_window_handle.
2024-06-10 18:40:33 +03:00
Mads Marquart
a298b4d00e Reduce usage of direct msg_send! 2024-06-10 18:40:33 +03:00
Mads Marquart
aebd5edc9e macOS: Move util::EMPTY_RANGE to usage spot (#3685) 2024-06-10 18:40:33 +03:00
Mads Marquart
c801b69d3e Retain ApplicationDelegate in NSWindowDelegate and NSView
The delegate is only weakly referenced by NSApplication, so getting it
from there may fail if the event loop has been dropped.

Fixes #3668.
2024-06-10 18:40:33 +03:00
Mads Marquart
ebd6454f8f Use rustc-check-cfg (#3682) 2024-06-10 18:40:33 +03:00
daxpedda
4f2f0bc08f Web: fix Clippy v1.78 FPs (#3678) 2024-06-10 18:40:33 +03:00
Kirill Chibisov
4b3c0655bf Winit version 0.30.0 2024-04-27 19:00:38 +04:00
Joshua Pedrick
0812adc983 Add UIGestureRecognizerDelegate and PanGestureRecogniser (#3597)
- Allow all gestures simultaneously recognized.
- Add PanGestureRecogniser with min/max number of touches.
- Fix sending delta values relative to Update instead to match macOS.
- Fix rotation gesture units from iOS to be in degrees instead of radians.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-27 19:00:38 +04:00
Mads Marquart
cd6ec19300 Don't set the background color when initializing with transparency (#3657)
Setting the background color changes how the window title bar appears,
which is something that the application should customize itself if it
wants this behaviour (and also, it wasn't set when calling
`set_transparent`, so the behaviour wasn't consistent).
2024-04-27 19:00:38 +04:00
growfrow
61bd8172bd chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 19:00:38 +04:00
Kirill Chibisov
c04c113e7e chore: ensure that .cargo config is not published
Just in case, so the correct changelog will be rendered when pulling
the crate from the crates.io as archive and trying to build it.
2024-04-27 19:00:38 +04:00
Marijn Suijten
ce32a3024e android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 19:00:38 +04:00
71 changed files with 2090 additions and 1534 deletions

8
.github/CODEOWNERS vendored
View File

@@ -1,6 +1,6 @@
# Android
/src/platform/android.rs @msiglreith @MarijnS95
/src/platform_impl/android @msiglreith @MarijnS95
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
# iOS
/src/platform/ios.rs @madsmtm
@@ -26,8 +26,8 @@
/src/platform_impl/web @daxpedda
# Windows
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
/src/platform/windows.rs @notgull
/src/platform_impl/windows @notgull
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51

View File

@@ -10,7 +10,7 @@ jobs:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: taiki-e/checkout-action@v1
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: taiki-e/checkout-action@v1
- uses: taiki-e/install-action@v2
with:
tool: typos-cli
@@ -88,7 +88,7 @@ jobs:
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: actions/checkout@v4
- uses: taiki-e/checkout-action@v1
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
@@ -220,9 +220,24 @@ jobs:
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: actions/checkout@v4
- uses: taiki-e/checkout-action@v1
- 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,8 +3,5 @@ target/
rls/
.vscode/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

12
.swcrc Normal file
View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.0"
version = "0.30.5"
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"] }
@@ -112,10 +112,12 @@ objc2 = "0.5.2"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
@@ -123,6 +125,7 @@ features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
@@ -140,6 +143,7 @@ features = [
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
@@ -235,7 +239,7 @@ features = [
[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"
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
@@ -245,18 +249,18 @@ rustix = { version = "0.38.4", default-features = false, features = [
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
sctk = { package = "smithay-client-toolkit", version = "0.19.2", 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.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.5", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [
wayland-client = { version = "0.31.4", optional = true }
wayland-protocols = { version = "0.32.2", features = [
"staging",
], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [
wayland-protocols-plasma = { version = "0.3.2", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", optional = true }
@@ -281,6 +285,7 @@ features = [
'AbortController',
'AbortSignal',
'Blob',
'BlobPropertyBag',
'console',
'CssStyleDeclaration',
'Document',
@@ -304,6 +309,7 @@ features = [
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Navigator',
'Node',
'PageTransitionEvent',
'PointerEvent',
@@ -316,6 +322,7 @@ features = [
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
]

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.0"
winit = "0.30.5"
```
## [Documentation](https://docs.rs/winit)
@@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room.
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).

View File

@@ -33,7 +33,6 @@ deny = []
skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
]
skip-tree = []

View File

@@ -3,75 +3,15 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowId};
use winit::window::Window;
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
}
impl ApplicationHandler for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
}
}
}
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
@@ -85,9 +25,58 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.create_window(window_attributes).unwrap()
}
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut app = Application::default();
event_loop.run_app(&mut app)
let mut parent_window_id = None;
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
},
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
},
_ => (),
}
})
}
#[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]

View File

@@ -17,9 +17,7 @@ use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{
DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, StartCause, WindowEvent,
};
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
@@ -214,6 +212,12 @@ 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();
@@ -305,12 +309,6 @@ impl Application {
}
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:?}");
}
@@ -328,7 +326,6 @@ impl ApplicationHandler<UserEvent> for Application {
match event {
WindowEvent::Resized(size) => {
info!("Resized({size:?})");
window.resize(size);
},
WindowEvent::Focused(focused) => {
@@ -343,7 +340,7 @@ impl ApplicationHandler<UserEvent> for Application {
},
WindowEvent::ThemeChanged(theme) => {
info!("Theme changed to {theme:?}");
window.set_theme(theme);
window.set_draw_theme(theme);
},
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
@@ -742,8 +739,8 @@ impl WindowState {
self.window.request_redraw();
}
/// Change the theme.
fn set_theme(&mut self, theme: Theme) {
/// Change the theme that things are drawn in.
fn set_draw_theme(&mut self, theme: Theme) {
self.theme = theme;
self.window.request_redraw();
}
@@ -893,6 +890,7 @@ enum Action {
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
@@ -924,6 +922,9 @@ 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",
@@ -1068,6 +1069,10 @@ 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

@@ -12,7 +12,6 @@ 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,19 +39,3 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### 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`.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
### 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

@@ -137,7 +137,7 @@
- On X11, non-resizable windows now have maximize explicitly disabled.
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
and `WindowEvent::HoveredFile`.
and `WindowEvent::HoveredFile`.
- On Mac, implement `DeviceEvent::Button`.
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.

View File

@@ -1,3 +1,78 @@
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4
### Changed
- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`.
### Fixed
- On Wayland, avoid crashing when compositor is misbehaving.
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
to not be de-duplicated.
- Account for different browser engine implementations of pointer movement coordinate space.
## 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`.
### 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
@@ -65,7 +140,7 @@
`Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code:
```rust,no_run,ignore
```rust,no_run
use winit::event::Event;
use winit::event_loop::EventLoop;
use winit::window::Window;

View File

@@ -3,20 +3,20 @@
- Added event `WindowEvent::HiDPIFactorChanged`.
- Added method `MonitorId::get_hidpi_factor`.
- Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of
`get_inner_size`.
`get_inner_size`.
- **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints,
but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`.
- `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`.
- Rewrite of the wayland backend to use wayland-client-0.11
- Support for dead keys on wayland for keyboard utf8 input
- Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
- On Windows added `MonitorIdExt::hmonitor` method
- Impl `Clone` for `EventsLoopProxy`
- `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found
- Support for touch event on wayland
- `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
- New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`.
- Send `CursorMoved` event after `CursorEntered` and `Focused` events.
- Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend.

View File

@@ -389,6 +389,8 @@ 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.
@@ -447,16 +449,13 @@ pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
/// # Notes
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
pub const fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
@@ -533,11 +532,11 @@ pub struct KeyEvent {
/// ## Caveats
///
/// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
/// implements DVORAK in hardware (or firmware)
/// implements DVORAK in hardware (or firmware)
/// - Your application will likely have to handle keyboards which are missing keys that your
/// own keyboard has.
/// own keyboard has.
/// - Certain `KeyCode`s will move between a couple of different positions depending on what
/// layout the keyboard was manufactured to support.
/// layout the keyboard was manufactured to support.
///
/// **Because of these caveats, it is important that you provide users with a way to configure
/// most (if not all) keybinds in your application.**
@@ -559,8 +558,7 @@ pub struct KeyEvent {
///
/// This has two use cases:
/// - Allows querying whether the current input is a Dead key.
/// - Allows handling key-bindings on platforms which don't
/// support [`key_without_modifiers`].
/// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`].
///
/// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
/// shortcuts, **it is important that you provide users with a way to configure your
@@ -568,8 +566,8 @@ pub struct KeyEvent {
/// incompatible keyboard layout.**
///
/// ## Platform-specific
/// - **Web:** Dead keys might be reported as the real key instead
/// of `Dead` depending on the browser/OS.
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
@@ -851,8 +849,8 @@ pub struct Touch {
///
/// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
/// - **Android**: This will never be [None]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1019,7 +1017,7 @@ mod tests {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
let did = event::DeviceId::dummy();
#[allow(deprecated)]
{
@@ -1029,7 +1027,7 @@ mod tests {
use crate::window::WindowId;
// Mainline events.
let wid = unsafe { WindowId::dummy() };
let wid = WindowId::dummy();
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
@@ -1158,7 +1156,7 @@ mod tests {
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
let did = crate::event::DeviceId::dummy().clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();

View File

@@ -20,9 +20,10 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError};
use crate::event::Event;
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
@@ -151,7 +152,6 @@ impl fmt::Debug for ActiveEventLoop {
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::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
@@ -209,110 +209,6 @@ 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.
@@ -320,6 +216,21 @@ impl<T> EventLoop<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// See [`run_app`].
///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// 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.
@@ -351,9 +262,7 @@ impl<T> EventLoop<T> {
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
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)
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -528,6 +437,17 @@ impl ActiveEventLoop {
self.p.listen_device_events(allowed);
}
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
pub fn system_theme(&self) -> Option<Theme> {
self.p.system_theme()
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
@@ -540,7 +460,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`LoopExiting`][crate::event::Event::LoopExiting].
/// See [`LoopExiting`][Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -709,3 +629,23 @@ impl AsyncRequestSerial {
Self { serial }
}
}
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserEvent(event) => app.user_event(event_loop, event),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
}
}

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.0",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! 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
@@ -76,12 +76,22 @@ use crate::window::{Window, WindowAttributes};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
impl<T> EventLoopExtAndroid for EventLoop<T> {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {}
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
@@ -100,7 +110,11 @@ impl WindowExtAndroid for Window {
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {
fn android_app(&self) -> &AndroidApp {
&self.p.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
@@ -108,9 +122,9 @@ pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
/// This must be called on Android since the [`AndroidApp`] is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
@@ -147,7 +161,7 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// For compatibility applications should then import the [`AndroidApp`] type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os = "android")]

View File

@@ -1,13 +1,12 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, 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.
@@ -53,18 +52,18 @@ pub trait EventLoopExtPumpEvents {
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
@@ -75,13 +74,13 @@ pub trait EventLoopExtPumpEvents {
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events are delivered via callbacks based on an event loop that is internal to the browser
/// itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
@@ -108,18 +107,30 @@ pub trait EventLoopExtPumpEvents {
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
) -> PumpStatus {
#[allow(deprecated)]
self.pump_events(timeout, |event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
/// See [`pump_app_events`].
///
/// [`pump_app_events`]: Self::pump_app_events
#[deprecated = "use EventLoopExtPumpEvents::pump_app_events"]
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
}
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,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.pump_events(timeout, event_handler)
}
}

View File

@@ -1,6 +1,7 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
@@ -8,10 +9,16 @@ 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;
/// See [`run_app_on_demand`].
///
/// [`run_app_on_demand`]: Self::run_app_on_demand
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -35,7 +42,9 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// backend it is possible to use `EventLoopExtWebSys::spawn()`
#[cfg_attr(not(web_platform), doc = "[^1]")]
/// more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -61,18 +70,23 @@ pub trait EventLoopExtRunOnDemand {
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
}
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> {
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.window_target().clear_exit();
self.event_loop.run_app_on_demand(app)
self.event_loop.run_on_demand(event_handler)
}
}

View File

@@ -55,7 +55,8 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource;
@@ -155,9 +156,7 @@ 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
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent: 'static;
/// Initializes the winit event loop.
@@ -183,13 +182,74 @@ pub trait EventLoopExtWebSys {
)]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// 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;
}
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 spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
}
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.spawn(event_handler)
}
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()
}
}
@@ -208,6 +268,20 @@ 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;
@@ -228,6 +302,16 @@ 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].
@@ -256,6 +340,29 @@ 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;
@@ -354,3 +461,5 @@ impl Display for CustomCursorError {
}
}
}
impl Error for CustomCursorError {}

View File

@@ -1,5 +1,3 @@
#![cfg(android_platform)]
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
@@ -14,13 +12,12 @@ use android_activity::{
};
use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
use crate::error::EventLoopError;
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
@@ -135,7 +132,7 @@ impl RedrawRequester {
pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> {
android_app: AndroidApp,
pub(crate) android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
@@ -198,28 +195,27 @@ impl<T: 'static> EventLoop<T> {
})
}
fn single_iteration<A: ApplicationHandler<T>>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
) {
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
trace!("Mainloop iteration");
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
app.new_events(self.window_target(), cause);
callback(event::Event::NewEvents(cause), self.window_target());
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
app.resumed(self.window_target());
callback(event::Event::Resumed, self.window_target());
},
MainEvent::TerminateWindow { .. } => {
app.suspended(self.window_target());
callback(event::Event::Suspended, self.window_target());
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
@@ -228,15 +224,23 @@ impl<T: 'static> EventLoop<T> {
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
},
self.window_target(),
);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(self.window_target(), window_id, event);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
},
self.window_target(),
);
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
@@ -246,19 +250,20 @@ impl<T: 'static> EventLoop<T> {
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
},
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
}
},
MainEvent::LowMemory => {
app.memory_warning(self.window_target());
callback(event::Event::MemoryWarning, self.window_target());
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
@@ -306,7 +311,7 @@ impl<T: 'static> EventLoop<T> {
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, app));
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
if !read_event {
break;
@@ -320,7 +325,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(self.window_target(), event);
callback(crate::event::Event::UserEvent(event), self.window_target());
}
}
@@ -333,32 +338,39 @@ impl<T: 'static> EventLoop<T> {
} else {
PhysicalSize::new(0, 0)
};
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
callback(event, self.window_target());
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::RedrawRequested,
};
callback(event, self.window_target());
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(self.window_target());
callback(event::Event::AboutToWait, self.window_target());
self.pending_redraw = pending_redraw;
}
fn handle_input_event<A: ApplicationHandler<T>>(
fn handle_input_event<F>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
app: &mut A,
) -> InputStatus {
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
@@ -396,16 +408,17 @@ impl<T: 'static> EventLoop<T> {
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
app.window_event(self.window_target(), window_id, event);
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
}),
};
callback(event, self.window_target());
}
}
},
@@ -433,22 +446,23 @@ impl<T: 'static> EventLoop<T> {
&mut self.combining_accent,
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(self.window_target(), window_id, event);
callback(event, self.window_target());
},
}
},
@@ -460,16 +474,19 @@ impl<T: 'static> EventLoop<T> {
input_status
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
self.run_on_demand(event_handler)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -483,11 +500,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
@@ -498,18 +514,18 @@ impl<T: 'static> EventLoop<T> {
self.cause = StartCause::Init;
// run the initial loop iteration
self.single_iteration(None, app);
self.single_iteration(None, &mut callback);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if self.exiting() {
self.loop_running = false;
app.exiting(self.window_target());
callback(event::Event::LoopExiting, self.window_target());
PumpStatus::Exit(0)
} else {
@@ -517,11 +533,10 @@ impl<T: 'static> EventLoop<T> {
}
}
fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
@@ -542,8 +557,8 @@ impl<T: 'static> EventLoop<T> {
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| {
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
@@ -584,7 +599,7 @@ impl<T: 'static> EventLoop<T> {
},
};
self.single_iteration(main_event, app);
self.single_iteration(main_event, &mut callback);
});
}
@@ -631,7 +646,7 @@ impl<T> EventLoopProxy<T> {
}
pub struct ActiveEventLoop {
app: AndroidApp,
pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
@@ -662,6 +677,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -1,11 +1,9 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2_ui_kit::{UIApplication, UIWindow};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::UIApplication;
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::event::{Event, WindowEvent};
use crate::window::WindowId as RootWindowId;
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
use crate::event::Event;
declare_class!(
pub struct AppDelegate;
@@ -40,35 +38,17 @@ declare_class!(
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
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);
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);
app_state::terminated(application);
}
#[method(applicationDidReceiveMemoryWarning:)]
@@ -78,26 +58,3 @@ declare_class!(
}
}
);
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

@@ -20,7 +20,7 @@ use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UICoordinateSpace, UIView};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalSize;
@@ -662,6 +662,28 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
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() {
@@ -696,7 +718,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub fn terminated(mtm: MainThreadMarker) {
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);
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
@@ -756,7 +798,7 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
f64::MAX,
0.000_000_1,
0,
0,
@@ -770,11 +812,11 @@ impl EventLoopWaker {
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {

View File

@@ -16,15 +16,14 @@ use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
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 crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource, Theme};
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
@@ -59,6 +58,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -109,28 +113,17 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &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 } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
(handler)(Event::UserEvent(event), window_target);
}
},
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),
}
}
@@ -173,7 +166,10 @@ impl<T: 'static> EventLoop<T> {
})
}
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -183,7 +179,7 @@ impl<T: 'static> EventLoop<T> {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(app, self.receiver);
let handler = map_user_event(handler, self.receiver);
let handler = unsafe {
std::mem::transmute::<
@@ -283,8 +279,7 @@ impl<T> EventLoopProxy<T> {
cancel: None,
perform: event_loop_proxy_handler,
};
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
@@ -367,7 +362,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
CFIndex::MIN,
control_flow_begin_handler,
ptr::null_mut(),
);
@@ -387,7 +382,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::max_value(),
CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);

View File

@@ -1,4 +1,3 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;
@@ -33,7 +32,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -701,7 +701,7 @@ pub struct WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
WindowId { window: std::ptr::null_mut() }
}
}

View File

@@ -11,8 +11,6 @@ use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use crate::application::ApplicationHandler;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::utils::Lazy;
use smol_str::SmolStr;
@@ -21,9 +19,12 @@ 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, EventLoopClosed};
use crate::event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::icon::Icon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::window::{
@@ -156,7 +157,7 @@ impl From<u64> for WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}
@@ -170,11 +171,11 @@ pub enum DeviceId {
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
return DeviceId::X(x11::DeviceId::dummy());
}
}
@@ -798,23 +799,25 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
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<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
self.run_on_demand(callback)
}
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 run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop {
@@ -894,6 +897,11 @@ impl ActiveEventLoop {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -14,7 +14,6 @@ use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
@@ -174,16 +173,12 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
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<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -205,27 +200,26 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
if !self.loop_running {
self.loop_running = true;
// Run the initial loop iteration.
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(&self.window_target);
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -233,11 +227,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let cause = loop {
let start = Instant::now();
@@ -299,10 +292,13 @@ impl<T: 'static> EventLoop<T> {
break cause;
};
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
@@ -313,18 +309,18 @@ impl<T: 'static> EventLoop<T> {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
app.new_events(&self.window_target, cause);
callback(Event::NewEvents(cause), &self.window_target);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.window_target);
callback(Event::Resumed, &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);
callback(Event::UserEvent(user_event), &self.window_target);
}
// Drain the pending compositor updates.
@@ -345,13 +341,18 @@ impl<T: 'static> EventLoop<T> {
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
let root_window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
};
app.window_event(&self.window_target, root_window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
@@ -394,14 +395,23 @@ impl<T: 'static> EventLoop<T> {
size
});
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::Resized(physical_size);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
}
if compositor_update.close_window {
let window_id = crate::window::WindowId(window_id);
app.window_event(&self.window_target, window_id, WindowEvent::CloseRequested);
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
}
}
@@ -410,15 +420,8 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Handle non-synthetic events.
@@ -426,15 +429,8 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Collect the window ids
@@ -470,8 +466,10 @@ impl<T: 'static> EventLoop<T> {
});
if let Some(event) = event {
let window_id = crate::window::WindowId(*window_id);
app.window_event(&self.window_target, window_id, event);
callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
}
}
@@ -481,7 +479,7 @@ impl<T: 'static> EventLoop<T> {
});
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.window_target);
callback(Event::AboutToWait, &self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;

View File

@@ -1,5 +1,3 @@
#![cfg(wayland_platform)]
//! Winit's Wayland backend.
use std::fmt::Display;
@@ -68,7 +66,7 @@ impl From<WaylandError> for OsError {
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -18,7 +18,6 @@ use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::seat::WinitSeatState;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
@@ -33,7 +32,17 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
) {
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return,
None => {
warn!("Received keyboard event {event:?} without seat");
return;
},
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => {
warn!("Received keyboard event {event:?} without keyboard");
return;
},
};
match event {
@@ -43,7 +52,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
warn!("non-xkb compatible keymap")
},
WlKeymapFormat::XkbV1 => {
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let context = &mut keyboard_state.xkb_context;
context.set_keymap_from_fd(fd, size as usize);
},
_ => unreachable!(),
@@ -67,7 +76,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
};
// Drop the repeat, if there were any.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -93,7 +101,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// NOTE: we should drop the repeat regardless whethere it was for the present
// window of for the window which just went gone.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -128,7 +135,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -136,7 +143,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let delay = match keyboard_state.repeat_info {
RepeatInfo::Repeat { delay, .. } => delay,
RepeatInfo::Disable => return,
@@ -163,18 +169,25 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to
// `None` is instant.
let repeat_keycode =
match seat_state.keyboard_state.as_ref().unwrap().current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to `None`
// is instant.
let repeat_keycode = match keyboard_state.current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
repeat_keycode,
@@ -183,7 +196,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
// NOTE: the gap could change dynamically while repeat is going.
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
match keyboard_state.repeat_info {
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
RepeatInfo::Disable => TimeoutAction::Drop,
}
@@ -194,7 +207,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -202,7 +215,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
if keyboard_state.repeat_info != RepeatInfo::Disable
&& keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key)
&& Some(key) == keyboard_state.current_repeat
@@ -216,7 +228,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
WlKeyboardEvent::Modifiers {
mods_depressed, mods_latched, mods_locked, group, ..
} => {
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let xkb_context = &mut keyboard_state.xkb_context;
let xkb_state = match xkb_context.state_mut() {
Some(state) => state,
None => return,
@@ -240,7 +252,6 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
},
WlKeyboardEvent::RepeatInfo { rate, delay } => {
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.repeat_info = if rate == 0 {
// Stop the repeat once we get a disable event.
keyboard_state.current_repeat = None;
@@ -348,7 +359,7 @@ impl KeyboardData {
}
fn key_input(
seat_state: &mut WinitSeatState,
keyboard_state: &mut KeyboardState,
event_sink: &mut EventSink,
data: &KeyboardData,
keycode: u32,
@@ -360,8 +371,6 @@ fn key_input(
None => return,
};
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);

View File

@@ -3,6 +3,7 @@
use std::sync::Arc;
use ahash::AHashMap;
use tracing::warn;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -76,7 +77,13 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::new_capability for unknown seat");
return;
},
};
match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
@@ -139,7 +146,13 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::remove_capability for unknown seat");
return;
},
};
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();

View File

@@ -4,6 +4,8 @@ use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::warn;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -41,7 +43,21 @@ impl PointerHandler for WinitState {
events: &[PointerEvent],
) {
let seat = pointer.winit_data().seat();
let seat_state = self.seats.get(&seat.id()).unwrap();
let seat_state = match self.seats.get(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received pointer event without seat");
return;
},
};
let themed_pointer = match seat_state.pointer.as_ref() {
Some(pointer) => pointer,
None => {
warn!("Received pointer event without pointer");
return;
},
};
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
@@ -78,9 +94,7 @@ impl PointerHandler for WinitState {
event.position.0,
event.position.1,
) {
if let Some(pointer) = seat_state.pointer.as_ref() {
let _ = pointer.set_cursor(connection, icon);
}
let _ = themed_pointer.set_cursor(connection, icon);
}
},
PointerEventKind::Leave { .. } if parent_surface != surface => {
@@ -113,9 +127,7 @@ impl PointerHandler for WinitState {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_entered(pointer);
}
window.pointer_entered(Arc::downgrade(themed_pointer));
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
@@ -126,9 +138,7 @@ impl PointerHandler for WinitState {
);
},
PointerEventKind::Leave { .. } => {
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_left(pointer);
}
window.pointer_left(Arc::downgrade(themed_pointer));
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
@@ -185,13 +195,13 @@ impl PointerHandler for WinitState {
// Mice events have both pixel and discrete delta's at the same time. So prefer
// the descrite values if they are present.
let delta = if has_discrete_scroll {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::LineDelta(
(-horizontal.discrete) as f32,
(-vertical.discrete) as f32,
)
} else {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::PixelDelta(
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
.to_physical(scale_factor),

View File

@@ -1,5 +1,7 @@
//! Touch handling.
use tracing::warn;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -31,11 +33,16 @@ impl TouchHandler for WinitState {
None => return,
};
let location = LogicalPosition::<f64>::from(position);
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::down without seat");
return;
},
};
// Update the state of the point.
let location = LogicalPosition::<f64>::from(position);
seat_state.touch_map.insert(id, TouchPoint { surface, location });
self.events_sink.push_window_event(
@@ -61,7 +68,13 @@ impl TouchHandler for WinitState {
_: u32,
id: i32,
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::up without seat");
return;
},
};
// Remove the touch point.
let touch_point = match seat_state.touch_map.remove(&id) {
@@ -98,7 +111,13 @@ impl TouchHandler for WinitState {
id: i32,
position: (f64, f64),
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::motion without seat");
return;
},
};
// Remove the touch point.
let touch_point = match seat_state.touch_map.get_mut(&id) {
@@ -129,7 +148,13 @@ impl TouchHandler for WinitState {
}
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::cancel without seat");
return;
},
};
for (id, touch_point) in seat_state.touch_map.drain() {
let window_id = wayland::make_wid(&touch_point.surface);

View File

@@ -339,12 +339,30 @@ impl CompositorHandler for WinitState {
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: &WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn scale_factor_changed(
&mut self,
_: &Connection,

View File

@@ -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_value() - 1)) == 1;
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
if !has_more_types {
let type_list = vec![
xev.data.get_long(2) as xproto::Atom,

View File

@@ -158,7 +158,9 @@ struct PreeditCallbacks {
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute(preedit_start_callback as usize)
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
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,5 +1,3 @@
#![cfg(x11_platform)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
@@ -27,7 +25,6 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
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, EventLoopClosed};
@@ -380,16 +377,12 @@ impl<T: 'static> EventLoop<T> {
&self.event_processor.target
}
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<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
let exit = loop {
match self.pump_app_events(None, app) {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -414,27 +407,26 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
if !self.loop_running {
self.loop_running = true;
// run the initial loop iteration
self.single_iteration(app, StartCause::Init);
self.single_iteration(&mut callback, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, app);
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
app.exiting(self.window_target());
callback(Event::LoopExiting, self.window_target());
PumpStatus::Exit(code)
} else {
@@ -448,11 +440,10 @@ impl<T: 'static> EventLoop<T> {
|| self.redraw_receiver.has_incoming()
}
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootAEL),
{
let start = Instant::now();
let has_pending = self.has_pending();
@@ -510,20 +501,23 @@ impl<T: 'static> EventLoop<T> {
return;
}
self.single_iteration(app, cause);
self.single_iteration(&mut callback, cause);
}
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootAEL),
{
callback(Event::NewEvents(cause), &self.event_processor.target);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
app.resumed(&self.event_processor.target)
callback(Event::Resumed, &self.event_processor.target);
}
// Process all pending events
self.drain_events(app);
self.drain_events(callback);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
@@ -533,12 +527,14 @@ impl<T: 'static> EventLoop<T> {
match token {
Some(Ok(token)) => {
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
},
};
app.window_event(&self.event_processor.target, window_id, event);
callback(event, &self.event_processor.target)
},
Some(Err(e)) => {
tracing::error!("Failed to get activation token: {}", e);
@@ -550,7 +546,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
app.user_event(&self.event_processor.target, event);
callback(Event::UserEvent(event), &self.event_processor.target);
}
}
@@ -564,24 +560,28 @@ impl<T: 'static> EventLoop<T> {
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
app.window_event(
callback(
Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested },
&self.event_processor.target,
window_id,
WindowEvent::RedrawRequested,
);
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.event_processor.target);
{
callback(Event::AboutToWait, &self.event_processor.target);
}
}
fn drain_events<A: ApplicationHandler<T>>(&mut self, app: &mut A) {
fn drain_events<F>(&mut self, callback: &mut F)
where
F: FnMut(Event<T>, &RootAEL),
{
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<T>| {
self.event_processor.process_event(&mut xev, |window_target, event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
@@ -590,15 +590,7 @@ impl<T: 'static> EventLoop<T> {
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
} else {
match event {
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)
},
_ => unreachable!("event which is neither device nor window event."),
}
callback(event, window_target);
}
});
}
@@ -779,7 +771,7 @@ pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId(0)
}
}

View File

@@ -4,7 +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_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
@@ -57,25 +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.queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
}
if delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
}
if delta_x != 0.0 || delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y) });
delegate.maybe_queue_device_event(DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
delegate.queue_device_event(DeviceEvent::Button {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
delegate.queue_device_event(DeviceEvent::Button {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
});

View File

@@ -1,40 +1,27 @@
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::rc::Weak;
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, NSObject, NSObjectProtocol, NSSize};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
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::{DeviceEvent, Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct Policy(NSApplicationActivationPolicy);
impl Default for Policy {
fn default() -> Self {
Self(NSApplicationActivationPolicy::Regular)
}
}
#[derive(Debug, Default)]
pub(super) struct State {
activation_policy: Policy,
pub(super) struct AppState {
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -50,7 +37,6 @@ pub(super) struct State {
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.
@@ -67,62 +53,20 @@ declare_class!(
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = State;
type Ivars = AppState;
}
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 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);
}
fn app_did_finish_launching(&self, notification: &NSNotification) {
self.did_finish_launching(notification)
}
#[method(applicationWillTerminate:)]
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();
fn app_will_terminate(&self, notification: &NSNotification) {
self.will_terminate(notification)
}
}
);
@@ -134,15 +78,78 @@ impl ApplicationDelegate {
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(State {
activation_policy: Policy(activation_policy),
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
default_menu,
activate_ignoring_other_apps,
..Default::default()
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![]),
});
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 =
@@ -193,7 +200,6 @@ 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.handle_event(Event::Suspended);
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
@@ -235,28 +241,16 @@ 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 maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), 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 maybe_queue_device_event(&self, event: DeviceEvent) {
self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
}
pub fn handle_redraw(&self, window_id: WindowId) {
@@ -284,9 +278,27 @@ impl ApplicationDelegate {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
unsafe { RunLoop::get() }.wakeup();
self.ivars().run_loop.wakeup();
}
#[track_caller]
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
// 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.handle_event(event);
} else {
tracing::debug!(?event, "had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || this.handle_event(event));
}
}
#[track_caller]
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
@@ -348,49 +360,6 @@ impl ApplicationDelegate {
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.handle_event(Event::WindowEvent {
@@ -421,17 +390,6 @@ 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;

View File

@@ -1,7 +1,7 @@
use std::cell::RefCell;
use std::{fmt, mem};
use super::app_delegate::HandlePendingUserEvents;
use super::app_state::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
@@ -16,7 +16,7 @@ impl fmt::Debug for EventHandlerData {
}
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
@@ -26,6 +26,10 @@ pub(crate) struct EventHandler {
}
impl EventHandler {
pub(crate) const 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

View File

@@ -16,16 +16,15 @@ use core_foundation::runloop::{
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send_id, ClassType};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::WinitApplication;
use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents};
use super::app_state::{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::Event;
use crate::event_loop::{
@@ -34,7 +33,7 @@ use crate::event_loop::{
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};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
@@ -107,6 +106,17 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -156,28 +166,17 @@ impl ActiveEventLoop {
}
}
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
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(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
app.user_event(window_target, event);
(handler)(Event::UserEvent(event), window_target);
}
},
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),
}
}
@@ -252,7 +251,7 @@ impl<T> EventLoop<T> {
});
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info));
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
@@ -272,19 +271,22 @@ impl<T> EventLoop<T> {
&self.window_target
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
self.run_on_demand(handler)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `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<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
let handler = map_user_event(app, self.receiver.clone());
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -319,12 +321,11 @@ impl<T> EventLoop<T> {
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
let handler = map_user_event(app, self.receiver.clone());
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -492,8 +493,7 @@ impl<T> EventLoopProxy<T> {
cancel: None,
perform: event_loop_proxy_handler,
};
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);

View File

@@ -2,7 +2,7 @@
mod util;
mod app;
mod app_delegate;
mod app_state;
mod cursor;
mod event;
mod event_handler;
@@ -37,7 +37,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -1,21 +1,28 @@
//! 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 core_foundation::base::{CFIndex, CFOptionFlags, CFRelease};
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
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_delegate::ApplicationDelegate;
use super::app_state::ApplicationDelegate;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
@@ -84,10 +91,20 @@ 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 unsafe fn get() -> Self {
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;
RunLoop(unsafe { CFRunLoopGetMain() })
}
@@ -114,9 +131,79 @@ impl RunLoop {
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
}
/// 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) }
}
}
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
@@ -125,16 +212,15 @@ pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
release: None,
copyDescription: None,
};
let run_loop = RunLoop::get();
run_loop.add_observer(
kCFRunLoopAfterWaiting,
CFIndex::min_value(),
CFIndex::MIN,
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::max_value(),
CFIndex::MAX,
control_flow_end_handler,
&mut context as *mut _,
);
@@ -165,8 +251,8 @@ impl Drop for EventLoopWaker {
}
}
impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
impl EventLoopWaker {
pub(crate) fn new() -> Self {
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.
@@ -174,7 +260,7 @@ impl Default for EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
f64::MAX,
0.000_000_1,
0,
0,
@@ -182,23 +268,21 @@ impl Default for EventLoopWaker {
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None }
Self { 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, std::f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, 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, std::f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
}

View File

@@ -16,7 +16,7 @@ use objc2_foundation::{
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_delegate::ApplicationDelegate;
use super::app_state::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,
@@ -270,19 +270,20 @@ 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, preedit_string) = if string.is_kind_of::<NSAttributedString>() {
let (marked_text, 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().to_string(),
string.string(),
)
} else {
let string: *const NSObject = string;
@@ -290,7 +291,7 @@ declare_class!(
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.to_string(),
string.copy(),
)
};
@@ -310,16 +311,21 @@ declare_class!(
self.ivars().ime_state.set(ImeState::Ground);
}
// 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() {
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.
None
} else {
Some((preedit_string.len(), preedit_string.len()))
// 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))
};
// Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)));
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[method(unmarkText)]
@@ -379,6 +385,7 @@ 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`.
@@ -679,7 +686,7 @@ declare_class!(
self.update_modifiers(event, false);
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -823,11 +830,7 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
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);
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn scale_factor(&self) -> f64 {

View File

@@ -74,7 +74,7 @@ impl Window {
pub struct WindowId(pub usize);
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}

View File

@@ -1,6 +1,9 @@
#![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;
@@ -8,28 +11,31 @@ 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, NSApplication,
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
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,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter,
NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
NSPoint, NSRect, NSSize, NSString,
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
use super::app_delegate::ApplicationDelegate;
use super::app_state::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::WindowEvent;
use crate::event::{InnerSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -77,8 +83,6 @@ 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.
@@ -184,7 +188,17 @@ declare_class!(
#[method(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
self.queue_static_scale_factor_changed_event();
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);
});
}
#[method(windowDidBecomeKey:)]
@@ -407,32 +421,66 @@ declare_class!(
}
}
// Key-Value Observing
unsafe impl WindowDelegate {
// 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,
)
};
}
#[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`");
#[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));
// 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:?}");
}
}
}
);
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,
@@ -601,6 +649,8 @@ 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.
@@ -654,15 +704,13 @@ impl WindowDelegate {
let scale_factor = window.backingScaleFactor() as _;
let current_theme = match attrs.preferred_theme {
Some(theme) => Some(theme),
None => Some(get_ns_theme(mtm)),
};
if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) {
unsafe { window.setAppearance(Some(&appearance)) };
}
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),
@@ -681,18 +729,23 @@ impl WindowDelegate {
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
delegate.queue_static_scale_factor_changed_event();
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
}
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
// Enable theme change event
let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() };
// Listen for theme change event.
//
// SAFETY: The observer is un-registered in the `Drop` of the delegate.
unsafe {
notification_center.addObserver_selector_name_object(
window.addObserver_forKeyPath_options_context(
&delegate,
sel!(effectiveAppearanceDidChange:),
Some(ns_string!("AppleInterfaceThemeChangedNotification")),
None,
ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
ptr::null_mut(),
)
};
@@ -754,24 +807,31 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
}
fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let app_delegate = &self.ivars().app_delegate;
let window = self.window();
self.ivars().previous_scale_factor.set(scale_factor);
let content_size = self.window().contentRectForFrameRect(self.window().frame()).size;
let content_size = window.contentRectForFrameRect(window.frame()).size;
let content_size = LogicalSize::new(content_size.width, content_size.height);
self.ivars().app_delegate.queue_static_scale_factor_changed_event(
self.window().retain(),
content_size.to_physical(scale_factor),
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged {
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);
}
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
}
fn emit_move_event(&self) {
@@ -799,7 +859,23 @@ impl WindowDelegate {
}
pub fn set_transparent(&self, transparent: bool) {
self.window().setOpaque(!transparent)
// 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));
}
pub fn set_blur(&self, blur: bool) {
@@ -898,8 +974,8 @@ impl WindowDelegate {
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
width: f32::MAX as f64,
height: f32::MAX as f64,
}));
let scale_factor = self.scale_factor();
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
@@ -1350,13 +1426,7 @@ impl WindowDelegate {
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
restore_and_release_display(&video_mode.monitor());
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
@@ -1387,13 +1457,7 @@ impl WindowDelegate {
);
app.setPresentationOptions(presentation_options);
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
restore_and_release_display(&video_mode.monitor());
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
@@ -1537,7 +1601,7 @@ impl WindowDelegate {
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::AppKitHandle::empty();
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.contentView().unwrap()) as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
rwh_04::RawWindowHandle::AppKit(window_handle)
}
@@ -1575,20 +1639,28 @@ 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> {
unsafe { self.window().appearance() }
.map(|appearance| appearance_to_theme(&appearance))
.or_else(|| {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
})
}
pub fn set_theme(&self, theme: Option<Theme>) {
let mtm = MainThreadMarker::from(self);
set_ns_theme(theme, mtm);
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) };
}
#[inline]
@@ -1609,6 +1681,21 @@ impl WindowDelegate {
}
}
fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
};
} else {
warn!(
monitor = monitor.name(),
"Tried to restore exclusive fullscreen on a monitor that is no longer available"
);
}
}
impl WindowExtMacOS for WindowDelegate {
#[inline]
fn simple_fullscreen(&self) -> bool {
@@ -1747,34 +1834,39 @@ impl WindowExtMacOS for WindowDelegate {
const DEFAULT_STANDARD_FRAME: NSRect =
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
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 dark_appearance_name() -> &'static NSString {
// Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
ns_string!("NSAppearanceNameDarkAqua")
}
pub 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
}
}
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()));
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
}
}

View File

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

View File

@@ -12,7 +12,6 @@ use orbclient::{
};
use smol_str::SmolStr;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
@@ -21,7 +20,7 @@ use crate::keyboard::{
PhysicalKey,
};
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
};
use super::{
@@ -323,13 +322,14 @@ impl<T: 'static> EventLoop<T> {
})
}
fn process_event<A: ApplicationHandler<T>>(
fn process_event<F>(
window_id: WindowId,
event_option: EventOption,
event_state: &mut EventState,
window_target: &event_loop::ActiveEventLoop,
app: &mut A,
) {
mut event_handler: F,
) where
F: FnMut(event::Event<T>),
{
match event_option {
EventOption::Key(KeyEvent { character, scancode, pressed }) => {
// Convert scancode
@@ -371,128 +371,125 @@ impl<T: 'static> EventLoop<T> {
key_without_modifiers = logical_key.clone();
}
let window_id = RootWindowId(window_id);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
},
is_synthetic: false,
},
is_synthetic: false,
};
app.window_event(window_target, window_id, event);
});
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
app.window_event(
window_target,
window_id,
event::WindowEvent::ModifiersChanged(event_state.modifiers()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
}
},
EventOption::TextInput(TextInputEvent { character }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
);
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Commit(character.into())),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
});
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Commit(character.into())),
});
},
EventOption::Mouse(MouseEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CursorMoved {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorMoved {
device_id: event::DeviceId(DeviceId),
position: (x, y).into(),
},
);
});
},
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
app.device_event(
window_target,
event::DeviceId(DeviceId),
event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
);
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
});
},
EventOption::Button(ButtonEvent { left, middle, right }) => {
while let Some((button, state)) = event_state.mouse(left, middle, right) {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseInput {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseInput {
device_id: event::DeviceId(DeviceId),
state,
button,
},
);
});
}
},
EventOption::Scroll(ScrollEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseWheel {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseWheel {
device_id: event::DeviceId(DeviceId),
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
},
);
});
},
EventOption::Quit(QuitEvent {}) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CloseRequested,
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CloseRequested,
});
},
EventOption::Focus(FocusEvent { focused }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Focused(focused),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Focused(focused),
});
},
EventOption::Move(MoveEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Moved((x, y).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((x, y).into()),
});
},
EventOption::Resize(ResizeEvent { width, height }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Resized((width, height).into()),
);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((width, height).into()),
});
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
},
// TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
let event = if entered {
event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId) }
if entered {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorEntered {
device_id: event::DeviceId(DeviceId),
},
});
} else {
event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId) }
};
app.window_event(window_target, RootWindowId(window_id), event);
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorLeft {
device_id: event::DeviceId(DeviceId),
},
});
}
},
other => {
tracing::warn!("unhandled event: {:?}", other);
@@ -500,13 +497,22 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
pub fn run<F>(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
let mut event_handler =
move |event: event::Event<T>, window_target: &event_loop::ActiveEventLoop| {
event_handler_inner(event, window_target);
};
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);
event_handler(event::Event::NewEvents(start_cause), &self.window_target);
if start_cause == StartCause::Init {
app.resumed(&self.window_target);
event_handler(event::Event::Resumed, &self.window_target);
}
// Handle window creates.
@@ -522,15 +528,23 @@ impl<T: 'static> EventLoop<T> {
self.windows.push((window, EventState::default()));
let window_id = RootWindowId(window_id);
// Send resize event on create to indicate first size.
let event = event::WindowEvent::Resized((properties.w, properties.h).into());
app.window_event(&self.window_target, window_id, event);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((properties.w, properties.h).into()),
},
&self.window_target,
);
// Send moved event on create to indicate first position.
let event = event::WindowEvent::Moved((properties.x, properties.y).into());
app.window_event(&self.window_target, window_id, event);
// Send resize event on create to indicate first position.
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((properties.x, properties.y).into()),
},
&self.window_target,
);
}
// Handle window destroys.
@@ -538,8 +552,14 @@ impl<T: 'static> EventLoop<T> {
let mut destroys = self.window_target.p.destroys.lock().unwrap();
destroys.pop_front()
} {
let window_id = RootWindowId(destroy_id);
app.window_event(&self.window_target, window_id, event::WindowEvent::Destroyed);
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(destroy_id),
event: event::WindowEvent::Destroyed,
},
&self.window_target,
);
self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd);
}
@@ -566,8 +586,7 @@ impl<T: 'static> EventLoop<T> {
window_id,
orbital_event.to_option(),
event_state,
&self.window_target,
app,
|event| event_handler(event, &self.window_target),
);
}
@@ -595,7 +614,7 @@ impl<T: 'static> EventLoop<T> {
}
while let Ok(event) = self.user_events_receiver.try_recv() {
app.user_event(&self.window_target, event);
event_handler(event::Event::UserEvent(event), &self.window_target);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -603,14 +622,16 @@ impl<T: 'static> EventLoop<T> {
let mut redraws = self.window_target.p.redraws.lock().unwrap();
redraws.pop_front()
} {
app.window_event(
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::RedrawRequested,
},
&self.window_target,
RootWindowId(window_id),
event::WindowEvent::RedrawRequested,
);
}
app.about_to_wait(&self.window_target);
event_handler(event::Event::AboutToWait, &self.window_target);
if self.window_target.p.exiting() {
break;
@@ -674,7 +695,7 @@ impl<T: 'static> EventLoop<T> {
}
}
app.exiting(&self.window_target);
event_handler(event::Event::LoopExiting, &self.window_target);
Ok(())
}
@@ -754,6 +775,11 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

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

View File

@@ -68,7 +68,12 @@ 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(closure) });
let closure = Closure(unsafe {
std::mem::transmute::<
Box<dyn FnOnce(&T) + Send>,
Box<dyn FnOnce(&T) + Send + 'static>,
>(closure)
});
self.0.send(closure);

View File

@@ -1,4 +1,5 @@
use std::future;
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Poll;
@@ -6,13 +7,13 @@ use std::task::Poll;
use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
struct Handler<T> {
value: T,
handler: fn(&T, usize),
handler: fn(&T, NonZeroUsize, bool),
}
#[derive(Clone)]
@@ -20,7 +21,11 @@ struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
pub fn new(
main_thread: MainThreadMarker,
value: T,
handler: fn(&T, NonZeroUsize, bool),
) -> Option<Self> {
let inner = Arc::new(Inner {
counter: AtomicUsize::new(0),
waker: AtomicWaker::new(),
@@ -37,7 +42,7 @@ impl<T> WakerSpawner<T> {
|handler, count| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count);
(handler.handler)(&handler.value, count, true);
},
{
let inner = Arc::clone(&inner);
@@ -46,29 +51,31 @@ impl<T> WakerSpawner<T> {
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());
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
inner.waker.register(cx.waker());
let count = inner.counter.swap(0, Ordering::Relaxed);
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(None);
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(None);
}
Poll::Pending
},
}
Poll::Pending
}
},
}
})
.await
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count);
(handler.handler)(&handler.value, count, false);
}
}
},
@@ -107,7 +114,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(1)
self.0.send(NonZeroUsize::MIN)
}
}

View File

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

View File

@@ -2,7 +2,7 @@
pub struct DeviceId(pub i32);
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}

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};
@@ -32,17 +32,33 @@ impl<T> EventLoop<T> {
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
}
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
pub fn run<F>(self, mut event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
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, &self.user_event_receiver, event));
let handler: Box<dyn FnMut(Event<()>)> = Box::new(|event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
});
// 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(handler) };
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event<()>)>, Box<dyn FnMut(Event<()>) + 'static>>(
handler,
)
};
self.elw.p.run(handler, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
@@ -54,12 +70,24 @@ impl<T> EventLoop<T> {
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler<T> + 'static>(self, mut app: A) {
pub fn spawn<F>(self, mut event_handler: F)
where
F: 'static + FnMut(Event<T>, &RootActiveEventLoop),
{
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
self.elw.p.run(
Box::new(move |event| {
handle_event(&mut app, &target, &self.user_event_receiver, event)
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
}),
true,
);
@@ -72,27 +100,20 @@ impl<T> EventLoop<T> {
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.elw
}
}
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::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),
Event::LoopExiting => app.exiting(target),
Event::MemoryWarning => app.memory_warning(target),
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()
}
}

View File

@@ -8,18 +8,21 @@ use crate::event::{
WindowEvent,
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::PollStrategy;
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
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::num::NonZeroUsize;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
@@ -40,6 +43,7 @@ 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>,
@@ -133,18 +137,20 @@ 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, count| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
}
})
.expect("`EventLoop` has to be created in the main thread");
let proxy_spawner =
WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_user_events(count, local)
}
})
.expect("`EventLoop` has to be created in the main thread");
Execution {
main_thread,
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),
@@ -239,21 +245,10 @@ impl Shared {
return;
}
let pointer_type = event.pointer_type();
if pointer_type != "mouse" {
return;
}
// chorded button event
let device_id = RootDeviceId(DeviceId(event.pointer_id()));
if let Some(button) = backend::event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
ElementState::Pressed
} else {
@@ -317,10 +312,6 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -340,10 +331,6 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -364,7 +351,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
device_id: RootDeviceId(DeviceId::dummy()),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Pressed,
@@ -382,7 +369,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
device_id: RootDeviceId(DeviceId::dummy()),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Released,
@@ -460,6 +447,51 @@ impl Shared {
self.send_events(iter::once(event));
}
// Add a series of user events 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_user_events(&self, count: NonZeroUsize, 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(ref runner)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
// If we're currently polling let `send_events` do its job.
if !matches!(runner.state, State::Poll { .. }) {
#[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_events(
iter::repeat(Event::UserEvent(())).take(count.get()),
)
}
}
})
.unchecked_into(),
);
return;
}
}
}
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
}
// 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
@@ -473,7 +505,7 @@ impl Shared {
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
Ok(RunnerEnum::Running(ref runner)) => {
// If we're currently polling, queue this and wait for the poll() method to be
// called
// called.
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
@@ -647,6 +679,7 @@ impl Shared {
start,
end,
_timeout: backend::Schedule::new_with_duration(
self.wait_until_strategy(),
self.window(),
move || cloned.resume_time_reached(start, end),
delay,
@@ -759,6 +792,14 @@ 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()
}

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};
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::platform_impl::platform::r#async::Waker;
use crate::window::{
@@ -142,7 +142,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
@@ -178,7 +178,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
@@ -258,21 +258,6 @@ impl ActiveEventLoop {
});
canvas.on_cursor_move(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -372,20 +357,6 @@ impl ActiveEventLoop {
);
canvas.on_mouse_press(
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
@@ -450,21 +421,6 @@ impl ActiveEventLoop {
);
canvas.on_mouse_release(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
});
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -605,7 +561,7 @@ impl ActiveEventLoop {
window_id: RootWindowId(id),
event: WindowEvent::Resized(new_size),
});
runner.request_redraw(RootWindowId(id));
canvas.request_animation_frame();
}
}
},
@@ -658,6 +614,16 @@ impl ActiveEventLoop {
self.runner.listen_device_events(allowed)
}
pub fn system_theme(&self) -> Option<Theme> {
backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| {
if is_dark_mode {
Theme::Dark
} else {
Theme::Light
}
})
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.runner.set_control_flow(control_flow)
}
@@ -682,6 +648,14 @@ 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,11 +28,9 @@ 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::{
@@ -43,6 +41,7 @@ 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

@@ -329,51 +329,29 @@ impl Canvas {
self.pointer_handler.on_cursor_enter(&self.common, handler)
}
pub fn on_mouse_release<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_mouse_release<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_release(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
)
self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler)
}
pub fn on_mouse_press<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_mouse_press<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_press(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
Rc::clone(&self.prevent_default),
)
}
pub fn on_cursor_move<MOD, M, T, B>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
button_handler: B,
) where
MOD: 'static + FnMut(ModifiersState),
pub fn on_cursor_move<M, T, B>(&mut self, mouse_handler: M, touch_handler: T, button_handler: B)
where
M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator<Item = PhysicalPosition<f64>>),
T: 'static
+ FnMut(ModifiersState, i32, &mut dyn Iterator<Item = (PhysicalPosition<f64>, Force)>),
@@ -381,7 +359,6 @@ impl Canvas {
{
self.pointer_handler.on_cursor_move(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
button_handler,
@@ -427,8 +404,8 @@ impl Canvas {
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
where
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
{
self.on_resize_scale = Some(ResizeScaleHandle::new(
self.window().clone(),

View File

@@ -1,13 +1,15 @@
use crate::dpi::LogicalPosition;
use crate::event::{MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use std::cell::OnceCell;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
use super::Engine;
bitflags::bitflags! {
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -95,42 +97,48 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
}
// TODO: Remove this when Firefox supports correct movement values in coalesced events.
// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers
// have agreed on what coordinate space `movementX/Y` is using.
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
pub struct MouseDelta(Option<MouseDeltaInner>);
pub struct MouseDeltaInner {
old_position: LogicalPosition<f64>,
old_delta: LogicalPosition<f64>,
// See <https://github.com/w3c/pointerlock/issues/42>.
pub enum MouseDelta {
Chromium,
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
Other,
}
impl MouseDelta {
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
// Firefox has wrong movement values in coalesced events, we will detect that by checking
// for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate`
// should require correct movement values, otherwise uncoalesced events might be broken as
// well.
Self((!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| {
MouseDeltaInner {
match super::engine(window) {
Some(Engine::Chromium) => Self::Chromium,
// Firefox has wrong movement values in coalesced events.
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
old_position: mouse_position(event),
old_delta: LogicalPosition {
x: event.movement_x() as f64,
y: event.movement_y() as f64,
},
}
}))
old_delta: LogicalPosition::new(
event.movement_x() as f64,
event.movement_y() as f64,
),
},
_ => Self::Other,
}
}
pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition<f64> {
if let Some(inner) = &mut self.0 {
let new_position = mouse_position(event);
let x = new_position.x - inner.old_position.x + inner.old_delta.x;
let y = new_position.y - inner.old_position.y + inner.old_delta.y;
inner.old_position = new_position;
inner.old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y)
} else {
LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64 }
pub fn delta(&mut self, event: &MouseEvent) -> Position {
match self {
MouseDelta::Chromium => {
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
},
MouseDelta::Gecko { old_position, old_delta } => {
let new_position = mouse_position(event);
let x = new_position.x - old_position.x + old_delta.x;
let y = new_position.y - old_position.y + old_delta.y;
*old_position = new_position;
*old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y).into()
},
MouseDelta::Other => {
LogicalPosition::new(event.movement_x(), event.movement_y()).into()
},
}
}
}
@@ -238,29 +246,6 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
}
}
// TODO: Remove when all browsers implement it correctly.
// See <https://github.com/rust-windowing/winit/issues/2875>.
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
thread_local! {
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
POINTER_RAW_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]
extern "C" {
type PointerRawSupport;
#[wasm_bindgen(method, getter, js_name = onpointerrawupdate)]
fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue;
}
let support: &PointerRawSupport = window.unchecked_ref();
!support.has_on_pointerrawupdate().is_undefined()
})
})
}
// TODO: Remove when Safari supports `getCoalescedEvents`.
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {

View File

@@ -9,6 +9,8 @@ mod pointer;
mod resize_scaling;
mod schedule;
use std::sync::OnceLock;
pub use self::canvas::{Canvas, Style};
pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle;
@@ -16,8 +18,13 @@ pub use self::resize_scaling::ResizeScaleHandle;
pub use self::schedule::Schedule;
use crate::dpi::{LogicalPosition, LogicalSize};
use js_sys::Array;
use wasm_bindgen::closure::Closure;
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast;
use web_sys::{
Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window,
};
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
@@ -158,3 +165,69 @@ pub fn is_visible(document: &Document) -> bool {
}
pub type RawCanvasType = HtmlCanvasElement;
#[derive(Clone, Copy)]
pub enum Engine {
Chromium,
Gecko,
WebKit,
}
pub fn engine(window: &Window) -> Option<Engine> {
static ENGINE: OnceLock<Option<Engine>> = OnceLock::new();
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Navigator)]
type NavigatorExt;
#[wasm_bindgen(method, getter, js_name = userAgentData)]
fn user_agent_data(this: &NavigatorExt) -> Option<NavigatorUaData>;
type NavigatorUaData;
#[wasm_bindgen(method, getter)]
fn brands(this: &NavigatorUaData) -> Array;
type NavigatorUaBrandVersion;
#[wasm_bindgen(method, getter)]
fn brand(this: &NavigatorUaBrandVersion) -> String;
}
*ENGINE.get_or_init(|| {
let navigator: NavigatorExt = window.navigator().unchecked_into();
if let Some(data) = navigator.user_agent_data() {
for brand in data
.brands()
.iter()
.map(NavigatorUaBrandVersion::unchecked_from_js)
.map(|brand| brand.brand())
{
match brand.as_str() {
"Chromium" => return Some(Engine::Chromium),
// TODO: verify when Firefox actually implements it.
"Gecko" => return Some(Engine::Gecko),
// TODO: verify when Safari actually implements it.
"WebKit" => return Some(Engine::WebKit),
_ => (),
}
}
None
} else {
let data = navigator.user_agent().ok()?;
if data.contains("Chrome/") {
Some(Engine::Chromium)
} else if data.contains("Gecko/") {
Some(Engine::Gecko)
} else if data.contains("AppleWebKit/") {
Some(Engine::WebKit)
} else {
None
}
}
})
}

View File

@@ -44,7 +44,7 @@ impl PointerHandler {
// touch events are handled separately
// handling them here would produce duplicate mouse events, inconsistent with
// other platforms.
let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id());
let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id());
handler(modifiers, pointer_id);
}));
@@ -61,20 +61,18 @@ impl PointerHandler {
// touch events are handled separately
// handling them here would produce duplicate mouse events, inconsistent with
// other platforms.
let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id());
let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id());
handler(modifiers, pointer_id);
}));
}
pub fn on_mouse_release<MOD, M, T>(
pub fn on_mouse_release<M, T>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
@@ -90,26 +88,23 @@ impl PointerHandler {
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
Force::Normalized(event.pressure() as f64),
),
"mouse" => mouse_handler(
_ => mouse_handler(
modifiers,
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
event::mouse_button(&event).expect("no mouse button released"),
),
_ => modifier_handler(modifiers),
}
}));
}
pub fn on_mouse_press<MOD, M, T>(
pub fn on_mouse_press<M, T>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
prevent_default: Rc<Cell<bool>>,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
@@ -125,8 +120,9 @@ impl PointerHandler {
}
let modifiers = event::mouse_modifiers(&event);
let pointer_type = &event.pointer_type();
match event.pointer_type().as_str() {
match pointer_type.as_str() {
"touch" => {
touch_handler(
modifiers,
@@ -135,7 +131,7 @@ impl PointerHandler {
Force::Normalized(event.pressure() as f64),
);
},
"mouse" => {
_ => {
mouse_handler(
modifiers,
event.pointer_id(),
@@ -143,27 +139,27 @@ impl PointerHandler {
event::mouse_button(&event).expect("no mouse button pressed"),
);
// Error is swallowed here since the error would occur every time the mouse
// is clicked when the cursor is grabbed, and there
// is probably not a situation where this could
// fail, that we care if it fails.
let _e = canvas.set_pointer_capture(event.pointer_id());
if pointer_type == "mouse" {
// Error is swallowed here since the error would occur every time the
// mouse is clicked when the cursor is
// grabbed, and there is probably not a
// situation where this could fail, that we
// care if it fails.
let _e = canvas.set_pointer_capture(event.pointer_id());
}
},
_ => modifier_handler(modifiers),
}
}));
}
pub fn on_cursor_move<MOD, M, T, B>(
pub fn on_cursor_move<M, T, B>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
mut button_handler: B,
prevent_default: Rc<Cell<bool>>,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator<Item = PhysicalPosition<f64>>),
T: 'static
+ FnMut(ModifiersState, i32, &mut dyn Iterator<Item = (PhysicalPosition<f64>, Force)>),
@@ -175,23 +171,10 @@ impl PointerHandler {
Some(canvas_common.add_event("pointermove", move |event: PointerEvent| {
let modifiers = event::mouse_modifiers(&event);
let pointer_type = event.pointer_type();
if let "touch" | "mouse" = pointer_type.as_str() {
} else {
modifier_handler(modifiers);
return;
}
let id = event.pointer_id();
// chorded button event
if let Some(button) = event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);
if prevent_default.get() {
// prevent text selection
event.prevent_default();
@@ -212,13 +195,7 @@ impl PointerHandler {
// pointer move event
let scale = super::scale_factor(&window);
match pointer_type.as_str() {
"mouse" => mouse_handler(
modifiers,
id,
&mut event::pointer_move_event(event)
.map(|event| event::mouse_position(&event).to_physical(scale)),
),
match event.pointer_type().as_str() {
"touch" => touch_handler(
modifiers,
id,
@@ -229,7 +206,12 @@ impl PointerHandler {
)
}),
),
_ => unreachable!("didn't return early before"),
_ => mouse_handler(
modifiers,
id,
&mut event::pointer_move_event(event)
.map(|event| event::mouse_position(&event).to_physical(scale)),
),
};
}));
}

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

View File

@@ -1,12 +1,14 @@
use js_sys::{Function, Object, Promise, Reflect};
use js_sys::{Array, 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, MessageChannel, MessagePort};
use web_sys::{
AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker,
};
use crate::platform::web::PollStrategy;
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
#[derive(Debug)]
pub struct Schedule {
@@ -29,6 +31,7 @@ enum Inner {
port: MessagePort,
_timeout_closure: Closure<dyn FnMut()>,
},
Worker(MessagePort),
}
impl Schedule {
@@ -45,14 +48,24 @@ impl Schedule {
}
}
pub fn new_with_duration<F>(window: &web_sys::Window, f: F, duration: Duration) -> Schedule
pub fn new_with_duration<F>(
strategy: WaitUntilStrategy,
window: &web_sys::Window,
f: F,
duration: Duration,
) -> Schedule
where
F: 'static + FnMut(),
{
if has_scheduler_support(window) {
Self::new_scheduler(window, f, Some(duration))
} else {
Self::new_timeout(window.clone(), f, Some(duration))
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),
}
}
@@ -153,6 +166,44 @@ 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_millis_ceil(duration)))
.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 {
@@ -165,6 +216,10 @@ impl Drop for Schedule {
port.close();
port.set_onmessage(None);
},
Inner::Worker(port) => {
port.close();
port.set_onmessage(None);
},
}
}
}
@@ -226,6 +281,29 @@ 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

@@ -0,0 +1,10 @@
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

@@ -0,0 +1 @@
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

@@ -431,7 +431,7 @@ impl Drop for Inner {
pub struct WindowId(pub(crate) u32);
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}

View File

@@ -123,7 +123,7 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
}
}
fn should_use_dark_mode() -> bool {
pub fn should_use_dark_mode() -> bool {
should_apps_use_dark_mode() && !is_high_contrast()
}

View File

@@ -57,7 +57,6 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
};
use crate::application::ApplicationHandler;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::EventLoopError;
use crate::event::{
@@ -82,7 +81,7 @@ use crate::platform_impl::platform::{
raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID,
};
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
};
use runner::{EventLoopRunner, EventLoopRunnerShared};
@@ -216,14 +215,17 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
self.run_on_demand(event_handler)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
{
let runner = &self.window_target.p.runner_shared;
@@ -234,32 +236,21 @@ impl<T: 'static> EventLoop<T> {
// 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)
},
// 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
// 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
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
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),
}
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
});
}
}
@@ -293,11 +284,10 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut event_handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
{
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
@@ -312,34 +302,16 @@ impl<T: 'static> EventLoop<T> {
// 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)
},
// 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),
}
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
user_event_receiver
.recv()
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
});
runner.wakeup();
}
}
@@ -578,6 +550,10 @@ impl ActiveEventLoop {
raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed);
}
pub fn system_theme(&self) -> Option<Theme> {
Some(if super::dark_mode::should_use_dark_mode() { Theme::Dark } else { Theme::Light })
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.runner_shared.set_control_flow(control_flow)
}
@@ -2469,7 +2445,7 @@ unsafe extern "system" fn thread_event_target_callback(
if userdata_removed {
drop(userdata);
} else {
Box::into_raw(userdata);
Box::leak(userdata);
}
result
}

View File

@@ -556,7 +556,7 @@ impl PartialKeyEventInfo {
// We convert dead keys into their character.
// The reason for this is that `key_without_modifiers` is designed for key-bindings,
// but the US International layout treats `'` (apostrophe) as a dead key and the
// reguar US layout treats it a character. In order for a single binding
// regular US layout treats it a character. In order for a single binding
// configuration to work with both layouts, we forward each dead key as a character.
Key::Dead(k) => {
if let Some(ch) = k {

View File

@@ -1,5 +1,3 @@
#![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};
@@ -69,7 +67,7 @@ unsafe impl Sync for PlatformSpecificWindowAttributes {}
pub struct DeviceId(u32);
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId(0)
}
}
@@ -105,7 +103,7 @@ unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
WindowId(0)
}
}

View File

@@ -263,39 +263,37 @@ pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
scancode_to_physicalkey(scancode as u32)
};
if keyboard.VKey == VK_SHIFT {
if let PhysicalKey::Code(code) = physical_key {
match code {
KeyCode::NumpadDecimal
| KeyCode::Numpad0
| KeyCode::Numpad1
| KeyCode::Numpad2
| KeyCode::Numpad3
| KeyCode::Numpad4
| KeyCode::Numpad5
| KeyCode::Numpad6
| KeyCode::Numpad7
| KeyCode::Numpad8
| KeyCode::Numpad9 => {
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
// wasn't active. The way this is exposed to applications by the system is that
// the application receives a fake key release event for the shift key at the
// moment when the numpad key is pressed, just before receiving the numpad key
// as well.
//
// The issue is that in the raw device event (here), the fake shift release
// event reports the numpad key as the scancode. Unfortunately, the event
// doesn't have any information to tell whether it's the
// left shift or the right shift that needs to get the fake
// release (or press) event so we don't forward this
// event to the application at all.
//
// For more on this, read the article by Raymond Chen, titled:
// "The shift key overrides NumLock"
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
return None;
},
_ => (),
}
if let PhysicalKey::Code(
KeyCode::NumpadDecimal
| KeyCode::Numpad0
| KeyCode::Numpad1
| KeyCode::Numpad2
| KeyCode::Numpad3
| KeyCode::Numpad4
| KeyCode::Numpad5
| KeyCode::Numpad6
| KeyCode::Numpad7
| KeyCode::Numpad8
| KeyCode::Numpad9,
) = physical_key
{
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
// wasn't active. The way this is exposed to applications by the system is that
// the application receives a fake key release event for the shift key at the
// moment when the numpad key is pressed, just before receiving the numpad key
// as well.
//
// The issue is that in the raw device event (here), the fake shift release
// event reports the numpad key as the scancode. Unfortunately, the event
// doesn't have any information to tell whether it's the
// left shift or the right shift that needs to get the fake
// release (or press) event so we don't forward this
// event to the application at all.
//
// For more on this, read the article by Raymond Chen, titled:
// "The shift key overrides NumLock"
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
return None;
}
}

View File

@@ -71,16 +71,13 @@ pub struct WindowId(pub(crate) platform_impl::WindowId);
impl WindowId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
/// # Notes
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real [`WindowId`].
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
WindowId(unsafe { platform_impl::WindowId::dummy() })
pub const fn dummy() -> Self {
WindowId(platform_impl::WindowId::dummy())
}
}
@@ -393,7 +390,6 @@ 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`.
@@ -421,8 +417,8 @@ impl WindowAttributes {
///
/// ## Platform-specific
///
/// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely
/// prevent all apps from reading the window content, for instance, QuickTime.
/// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all
/// apps from reading the window content, for instance, QuickTime.
/// - **iOS / Android / Web / x11 / Orbital:** Ignored.
///
/// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone
@@ -470,8 +466,8 @@ impl WindowAttributes {
/// ## Platform-specific
///
/// - **Windows** : A child window has the WS_CHILD style and is confined
/// to the client area of its parent window. For more information, see
/// <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
/// to the client area of its parent window. For more information, see
/// <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
/// - **X11**: A child window is confined to the client area of its parent window.
/// - **Android / iOS / Wayland / Web:** Unsupported.
#[cfg(feature = "rwh_06")]
@@ -534,9 +530,9 @@ impl Window {
/// provided by XRandR.
///
/// If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use
/// the XRandR scaling method. Generally speaking, you should try to configure the
/// standard system variables to do what you want before resorting to
/// `WINIT_X11_SCALE_FACTOR`.
/// the XRandR scaling method. Generally speaking, you should try to configure the
/// standard system variables to do what you want before resorting to
/// `WINIT_X11_SCALE_FACTOR`.
/// - **Wayland:** The scale factor is suggested by the compositor for each window individually
/// by using the wp-fractional-scale protocol if available. Falls back to integer-scale
/// factors otherwise.
@@ -632,7 +628,8 @@ impl Window {
///
/// ## Platform-specific
///
/// **Wayland:** - schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
/// - **Android / iOS / X11 / Web / Windows / macOS / Orbital:** Unsupported.
/// - **Wayland:** Schedules a frame callback to throttle [`WindowEvent::RedrawRequested`].
///
/// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested
#[inline]
@@ -941,8 +938,7 @@ impl Window {
///
/// ## Platform-specific
///
/// - **macOS:** If you're not drawing to the window yourself, you might have to set the
/// background color of the window to enable transparency.
/// - **macOS:** This will reset the window's background color.
/// - **Web / iOS / Android:** Unsupported.
/// - **X11:** Can only be set while building the window, with
/// [`WindowAttributes::with_transparent`].
@@ -1354,11 +1350,12 @@ impl Window {
self.window.maybe_queue_on_main(move |w| w.request_user_attention(request_type))
}
/// Sets the current window theme. Use `None` to fallback to system default.
/// Set or override the window theme.
///
/// Specify `None` to reset the theme to the 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
@@ -1376,10 +1373,12 @@ impl Window {
/// Returns the current window theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **macOS:** This is an app-wide setting.
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
/// - **iOS / Android / x11 / Orbital:** Unsupported.
/// - **Wayland:** Only returns theme overrides.
#[inline]
pub fn theme(&self) -> Option<Theme> {
let _span = tracing::debug_span!("winit::Window::theme",).entered();
@@ -1390,8 +1389,8 @@ impl Window {
///
/// ## Platform-specific
///
/// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely
/// prevent all apps from reading the window content, for instance, QuickTime.
/// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all
/// apps from reading the window content, for instance, QuickTime.
/// - **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported.
///
/// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone