Compare commits

...

157 Commits
it ... v0.29.2

Author SHA1 Message Date
Kirill Chibisov
d82886bddc Winit version 0.29.2 2023-10-21 11:40:41 +04:00
Kirill Chibisov
08edda1b0b On X11, fix cursor_hittest not reloaded on Resize
The cursor hittest was not reloaded on window size changes, only
when `Window::request_inner_size` was called leading to regions
of the window being not clickable.

Also, don't try to apply hittest logic when user never requested a
hittest.

Links: https://github.com/alacritty/alacritty/pull/7220
2023-10-21 11:40:41 +04:00
Diggory Hardy
7de33bca40 Fix rwhd_05 doc links 2023-10-21 11:40:41 +04:00
Valaphee The Meerkat
40ba9a7ce7 feat(windows): Fix inconsistency in mouse button device events, add hwheel device event on Windows
While working with device events, I noticed that there was an inconsistency in the mouse button device events between Windows/X11 and for example web, because web uses the same ids/order as the MouseButton enum, and Windows/X11 are using the X11 ids, and hwheel device event was ignored on Windows.

Mouse button device events are now using the same order as the MouseButton enum, and I also added hwheel device events for Windows.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
0656c54c3b On Windows, fix IME APIs MT-safety
Execute the calls to the IME from the main thread.

Fixes #3123.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
74fcf7f9c0 On Windows, fix RedrawRequested delivery
When calling `Window::request_redraw` from the `RedrawRequested`
handler the `RedrawWindow` won't result in `WM_PAINT` being delivered
due since user callback is run before `DefWindowProcW` is called.

Track whether the user called `Window::request_redraw` and ask for
`RedrawWindow` after running the said function during `WM_PAINT`
handling.

Fixes #3150.
2023-10-21 11:40:41 +04:00
Diggory Hardy
0bc8f5e33a Implement Ord/PartialOrd for ModifiersState 2023-10-21 11:40:41 +04:00
Xiaopeng Li
f6cc6c1472 On Windows, fix invalid hmonitor panic
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-21 11:40:41 +04:00
Arend van Beelen jr
20384d2f02 On iOS, add configuration for status bar style
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-10-21 11:40:41 +04:00
Kirill Chibisov
cdee616812 On macOS, fix tabGroup misuse
The property is marked as `Weak`, however we used strong `Id`.

Links: https://github.com/alacritty/alacritty/issues/7249
2023-10-21 11:40:41 +04:00
Kirill Chibisov
f58fb69446 Remove garbage from README
The docs are in the src/lib.rs anyway and are present on docs.rs.
2023-10-21 11:40:41 +04:00
Diggory Hardy
6b445219c1 Revise Key and KeyCode enums
Split `Key` into clear categories, like `Named`, `Dead`, Character`, `Unidentified`
removing the `#[non_exhaustive]` from the `Key` itself.

Similar action was done for the `KeyCode`.

Fixes: #2995
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-21 11:40:41 +04:00
Kirill Chibisov
18b8569161 Ensure that DISPLAY vars are non-empty before using
It's common to disable Wayland by `WAYLAND_DISPLAY= <application>`.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
08b0464ac3 Fix examples not render on Wayland
The `rwh_05` feature was not enabled.

Fixes: e41fac825c (Update to new raw-window-handle strategy)
2023-10-21 11:40:41 +04:00
YouKnow
df2f5adfba On Windows, fix CursorEntered/CursorLeft not sent during mouse grab
Fixes #3153.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
99f86d729f On macOS, fix globe key triggering assertion
Sometimes FlagsChanged events don't carry any KeyCode information, thus
we can't create a synthetic presses events for them.

However in such cases, modifiers information is still accurate, thus
propagate it.

Fixes #2872.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
d06deeecf6 Make WindowBuilder Send + Sync
Window builder is always accessed by winit on the thread event loop
is on, thus it's safe to mark the data it gets as `Send + Sync`.
Each unsafe object is marked individually as `Send + Sync` instead
of just implementing `Send` and `Sync` for the whole builder.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
e6d2fd7287 Remove resolved deny.toml entries 2023-10-21 11:40:41 +04:00
Marijn Suijten
f2edd23542 Upgrade to ndk 0.8, ndk-sys 0.5 + android-activity 0.5 releases
Fixes #2905.
Co-authored-by: Robert Bragg <robert@sixbynine.org>
2023-10-21 11:40:41 +04:00
daxpedda
70e6ddd210 Web Async Rework (#3082) 2023-10-21 11:40:41 +04:00
Kirill Chibisov
f3fb27c17b Add a note on Window::request_redraw on Windows
Fixing this could require a massive rework to how redraw is handled
on windows to the point of removing `WM_PAINT`, since it's not reliable
by any means for our use case.

For now at least document that the API is broken. It was broken like
that for a long while.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
75b463a368 Implement AsFd/AsRawFd for EventLoop<T>
This should help other crates to integrate winit's event loop into
their bigger event loop without adding an extra thread.
2023-10-21 11:40:41 +04:00
John Nunley
ea8604e175 Fix potentially unaligned references in X11 device
Fixes #3125
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-21 11:40:41 +04:00
Kirill Chibisov
b1bd0f77fb Update SCTK to 0.18.0
The update is pretty minor, however we support now
`WindowEvent::Occluded` when xdg-shell v6 is available.

It also adds support for `Window::show_window_menu`.

Fixes #2927.
2023-10-21 11:40:41 +04:00
Kirill Chibisov
1fded249d0 Fix ndk deps versions 2023-10-21 11:40:41 +04:00
John Nunley
349a3e7b8c Update to new raw-window-handle strategy
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: TornaxO7 <tornax@proton.me>
2023-10-21 11:40:41 +04:00
Ryan Hileman
f2d277e599 feat: Implement set_cursor_hittest for X11 2023-10-21 11:40:41 +04:00
Kirill Chibisov
8d5d612456 Fix CHANGELOG entry for Event::MemoryWarning
While the changelog entries for beta releases doesn't really matter. The
change wasn't marked as breaking, while it is.

Fixes: 93f1000a0 (Add Occluded and MemoryWarning events for iOS/Android)
2023-10-21 11:40:41 +04:00
François
5788319632 Add Occluded and MemoryWarning events for iOS/Android
Hook `Occluded` event to foreground/background evens on iOS.

This commit also enabled the `MemoryWarning` event, since it's
emitted from the windowing system.

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-21 11:40:41 +04:00
YouKnow
976023bfc0 Add Window::show_window_menu
Add a method to request a system menu. The implementation
is provided only on Windows for now.

Co-authored-by: daxpedda <daxpedda@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-21 11:40:41 +04:00
daxpedda
0f9b95814e Fix reset to Poll after the event loop starts 2023-10-21 11:40:41 +04:00
baneyue
112dcc808a On Wayland, fix MonitorHandle position 2023-10-21 11:40:41 +04:00
Kirill Chibisov
4a381fb1db Remove obsolete docs about wayland CSD env variable
The env variable was removed a while ago, yet it was still present in
the user docs.
2023-10-21 11:40:41 +04:00
daxpedda
8339ddf368 Web: fix ControlFlow::WaitUntil to never wake up **before** the given time (#3133) 2023-10-21 11:40:41 +04:00
Dmitry Sharshakov
b41f01c990 Add Window::set_blur
Allow clients to request blur behind their window, implemented on
Wayland for now.
2023-10-21 11:40:41 +04:00
daxpedda
570f3101e5 Web: remove unnecessary usage of once_cell::unsync::Lazy (#3134) 2023-10-21 11:40:41 +04:00
daxpedda
3923c59fd8 Update Clippy to v1.73 (#3135) 2023-10-21 11:40:41 +04:00
epimeletes
75ae402a24 Rename run_ondemand to run_on_demand 2023-10-21 11:40:41 +04:00
Fredrik Fornwall
4385c17cbb Make DeviceId contain device id's on Android 2023-10-21 11:40:41 +04:00
Mads Marquart
3af256260e Link to areweguiyet.com and arewegameyet.rs for extra deps 2023-10-21 11:40:41 +04:00
Mads Marquart
d9363219e1 X11: Add #[deny(unsafe_op_in_unsafe_fn)] (#3121)
* X11: Add #[deny(unsafe_op_in_unsafe_fn)]

* Enable #![deny(unsafe_op_in_unsafe_fn)] everywhere
2023-10-21 11:40:41 +04:00
Mads Marquart
a52a6d47ca Windows: Add #[deny(unsafe_op_in_unsafe_fn)] (#3070) 2023-10-21 11:40:41 +04:00
Mads Marquart
ec83de3938 Bump version on master (#3119)
This commit does not represent a release and only synchronizes CHANGELOG from the latest release.
2023-10-21 11:40:41 +04:00
Neil Macneale V
43d6eac871 Fix transparent windows on X11 2023-10-21 11:40:41 +04:00
Kirill Chibisov
1f101b2654 Remove DeviceEvent::Text event
The event is never constructed inside the winit.
2023-10-21 11:40:41 +04:00
lucasmerlin
709929fcab Pass force on touch events on android 2023-10-21 11:40:41 +04:00
daxpedda
220a2d32d5 Make ControlFlow::Wait the default (#3106) 2023-10-21 11:40:41 +04:00
Kirill Chibisov
c5cef46060 Remove old docs about EventLoop::run 2023-10-21 11:40:41 +04:00
Pavel Strakhov
367a2ae057 On X11, fix WaitUntil and Poll behavior
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-10-21 11:40:41 +04:00
StarStarJ
e038597e81 Implement PartialOrd and Ord for MouseButton 2023-10-21 11:40:41 +04:00
Kirill Chibisov
27cd20739d Correct Wayland section in dpi docs
Fixes #3100.
2023-10-21 11:40:41 +04:00
daxpedda
8d9fd3d3d6 Install cargo-apk with the stable toolchain 2023-10-21 11:40:41 +04:00
daxpedda
56427e47a7 Ignore foreign-types* duplicate deps on macOS
The dependency is duplicated due to examples, yet we still need to
exclude checking it.

Fixes #3093.
2023-10-21 11:40:41 +04:00
daxpedda
c744b9aea5 Rename PollType to PollStrategy (#3089) 2023-10-21 11:40:41 +04:00
John Nunley
ef9ed71f1b Add an MSRV policy to the README (#3046) 2023-10-21 11:40:41 +04:00
daxpedda
dda8053bd3 Add Window.requestIdleCallback() support (#3084) 2023-10-21 11:40:41 +04:00
Fredrik Fornwall
779212da33 Correct set_exit() -> exit() in the changelog (#3088) 2023-10-21 11:40:41 +04:00
John Nunley
28552c9cc1 Revert select_xkb_events to its previous impl
The new implementation of select_xkb_events apparently misconfigures
the server. This commit does a temporary fix by just reverting it to its
previous implementation.

This is temporary until I can figure out what Xlib is doing behind the
scenes or until I read xkbproto.pdf.

Fixes: #3079
Signed-off-by: John Nunley <dev@notgull.net>
2023-10-21 11:40:41 +04:00
daxpedda
48647b506f Move ControlFlow to EventLoopWindowTarget
Fixes #3042.
2023-10-21 11:40:41 +04:00
John Nunley
42243ce288 Allow the user to force X11 under Wayland
Use forced backend over the env variables. 

Signed-off-by: John Nunley <dev@notgull.net>
Fixes: #3057
2023-10-21 11:40:41 +04:00
daxpedda
84d9bfd59e Remove T from EventLoopTargetWindow (#3081)
Co-authored-by: nerditation <12248559+nerditation@users.noreply.github.com>
2023-10-21 11:40:41 +04:00
Kirill Chibisov
cd5c1fb724 Mark startup_notify unsafe functions as safe
They are safe, since they use the rust `std::env` stuff. Making them
safe lets downstream to determine that `std::env` is used and not the
`libc` env manipulation routines, which are unsafe.
2023-10-21 11:40:41 +04:00
Mads Marquart
8455f3415e Slightly reduce number of cfgs (#3071)
* Make Linux platforms less dependent on the root monitor handle

* Add various functions to the Wayland platform to reduce cfgs

* Don't use a cfg in listen_device_events

* Don't use a cfg in set_content_protected

* Fix instance of a target_os cfg
2023-10-21 11:40:41 +04:00
Kirill Chibisov
c59d6bc809 On Wayland, fix TouchPhase::Canceled sent for Move
Fixes #3035.
2023-10-21 11:40:41 +04:00
Mads Marquart
4681133eca Make EventLoopWindowTarget independent of the user type on Orbital (#3055) 2023-10-21 11:40:41 +04:00
Mads Marquart
b278aa859f Ensure that winit initializes NSApplication (#3069) 2023-10-21 11:40:41 +04:00
Mads Marquart
ee4ec43cf3 Fix missing quote (#3068) 2023-10-21 11:40:41 +04:00
John Nunley
25b629f117 Implement X11 extensions using x11rb instead of Xlib
Removes Xlib code by replacing it with the x11rb equivalent,
the commit handles xrandr, xinput, xinput2, and xkb.

Signed-off-by: John Nunley <dev@notgull.net>
2023-10-21 11:40:41 +04:00
daxpedda
4b30f9ce22 Web: Fullscreen Overhaul (#3063) 2023-10-21 11:40:41 +04:00
daxpedda
2428224c09 Enable event propagation (#3062) 2023-10-21 11:40:41 +04:00
Mads Marquart
2d9b852a95 Fix macOS deminiaturize (#3054) 2023-10-21 11:40:41 +04:00
Kirill Chibisov
246d53d5a1 Lock the cargo-apk deps on CI 2023-10-21 11:40:41 +04:00
Mads Marquart
865afd22be Make iOS fully thread safe (#3045)
* macOS & iOS: Refactor EventWrapper

* macOS & iOS: Make EventLoopWindowTarget independent of the user event

* iOS: Use MainThreadMarker instead of marking functions unsafe

* Make iOS thread safe
2023-10-21 11:40:41 +04:00
Mads Marquart
05130cb329 Improve CI caching, and give each job names
This improves CI performance by around 20% (down from 10 to 8 minutes).
2023-10-21 11:40:41 +04:00
daxpedda
a1a6f7baf9 Move Event::RedrawRequested to WindowEvent (#3049) 2023-10-21 11:40:41 +04:00
daxpedda
f160a6003c On Web, never return a MonitorHandle (#3051) 2023-10-21 11:40:41 +04:00
daxpedda
a24d092fa1 Use setTimeout() trick instead of Window.requestIdleCallback() (#3044) 2023-10-21 11:40:41 +04:00
Mads Marquart
a8a0462c0d Use frame instead of visibleRect (#3043) 2023-10-21 11:40:41 +04:00
Mads Marquart
647c320ca7 Fix recent CI failures (#3041)
* Fix new clippy lints

* Fix nightly documentation warnings
2023-10-21 11:40:41 +04:00
StarStarJ
5144337253 Implement PartialOrd/Ord for KeyCode/NativeKeyCode 2023-10-21 11:40:41 +04:00
Kirill Chibisov
7f1aaa652d Winit version 0.29.1-beta
Cargo automatically pulls pre-releases, so bump semver for each
pre-release as well, since those can't be resolved automatically.

We don't do betas for the patch bumps, so it should work just fine.
2023-08-16 15:57:50 +04:00
Kirill Chibisov
00b5de0a68 Winit version 0.29.0-beta.1 2023-08-16 12:40:18 +04:00
Kirill Chibisov
80d1e49354 Use beta versions of android crates 2023-08-16 12:39:57 +04:00
Kirill Chibisov
07dd45f8e3 Bump MSRV to 1.65 2023-08-16 12:39:56 +04:00
Mads Marquart
4e6ce00ec5 Improve macOS/iOS/Web thread safety
Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
65c2482d74 Pin android-activity git dependency 2023-08-15 13:10:02 +04:00
Kirill Chibisov
ba2bfd064f Reexport raw-window-handle in window module
We use raw-window-handle extensive in the public API as well as we
force the users to use it to get some essential data for interop, thus
reexport it.

Fixes: #2913.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
08ad3f19e2 Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users it's not desirable.

The inner panics were left only when they are used to `assert!` during
development.

This reverts commit 9f91bc413fe20618bd7090829832bb074aab15c3 which
reverted the original patch which was merged without a proper review.

Fixes: #500.
2023-08-15 13:10:02 +04:00
lucasmerlin
e3fbfd6792 Fix touch force for Apple Pencil 2023-08-15 13:10:02 +04:00
John Nunley
c40af0062b Add a way to embed the X11 window into another
Signed-off-by: John Nunley <dev@notgull.net>
Tested-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-15 13:10:02 +04:00
Fredrik Fornwall
511bf53889 iOS: Use NSOperatingSystemVersion from icrate (#3019) 2023-08-15 13:10:02 +04:00
Robert Bragg
7451c4b88c Android: Support unicode character mapping + dead keys
Up until now the Android backend has been directly mapping key codes
which essentially just represent the "physical" cap of the key (quoted
since this also related to virtual keyboards).

Since we didn't account for any meta keys either it meant the backend
only supported a 1:1 mapping from key codes, which only covers a tiny
subset of characters. For example you couldn't type a colon since
there's no keycode for that and we didn't try and map Shift+Semicolon
into a colon character.

This has been tricky to support because the `NativeActivity` class doesn't
have direct access to the Java `KeyEvent` object which exposes a more
convenient `getUnicodeChar` API.

It is now possible to query a `KeyCharcterMap` for the device associated
with a `KeyEvent` via the `AndroidApp::device_key_character_map` API
which provides a binding to the SDK `KeyCharacterMap` API in Java:

 https://developer.android.com/reference/android/view/KeyCharacterMap

This is effectively what `getUnicodeChar` is implemented based on and is
a bit more general purpose.

`KeyCharacterMap` lets us map a key_code + meta_state from a `KeyEvent`
into either a unicode character or dead key accent that can be combined
with the following key. This mapping is done based on the user's chosen
layout for the keyboard.

To enable support for key character maps the
`AndroidApp::input_events()` API was replaced by
`AndroidApp::input_events_iter()` which returns a (lending) iterator for
events. This was changed because the previous design made it difficult
to allow other AndroidApp APIs to be used while iterating events (mainly
because AndroidApp held a lock over the backend during iteration)
2023-08-15 13:10:02 +04:00
Kirill Chibisov
42ecef7b31 Fix event loop not waking up due to repeat source
Force the wake up from the repeat source as well.

Fixes: cad327755 (On Wayland, reduce amount of spurious wakeups)
2023-08-15 13:10:02 +04:00
Kirill Chibisov
5d9ce7f5f4 On X11, set visual_id in raw-window-handle
Fixes #2681.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
ef5b71d658 Revert "Propagate error from EventLoop creation" (#3010)
This reverts commit ed26dd58fd.
The patched was merged with a review by accident.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
4ab36f336c Propagate error from EventLoop creation
Inner panics could make it hard to trouble shoot the issues and for some
users ints not desirable.

The inner panics were left only when they are used to `assert!` during
development.
2023-08-15 13:10:02 +04:00
John Nunley
2791cbd65e Make with_x11_visual take ID instead of a pointer
At the moment, the with_x11_visual function takes a pointer and
immediately dereferences it to get the visual info inside. As it is safe
to pass a null pointer to this function, it is unsound. This commit
replaces the pointer parameter with a visual ID, and then uses that ID
to look up the actual visual under
the X11 setup. As this is what was already practically happening before,
this change shouldn't cause any performance downgrades.

This is a breaking change, but it's done in the name of soundness so it
should be okay. It should be trivial for end users to accommodate it,
as it's just a matter of getting the visual ID from the pointer to the
visual before passing it in.

Signed-off-by: John Nunley <dev@notgull.net>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
03bf83f45e Remove 'static requirement on run
There's no need to force the static on the users, given that internally
some backends were not using static in the first place.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
02870202cb On Wayland, reduce amount of spurious wakeups
Mark it as breaking, since some clients relied on that behavior, simply
because dispatching clients queue always woke up a winit, meaning that
they won't be able to use user events for this sake.
2023-08-15 13:10:02 +04:00
Mads Marquart
c268922def Remove functionality already exposed through raw-window-handle
Nothing changed from the user point of view, other than they should
use the `raw-window-handle`, which is objectively better, given that
it reduces the amount of `cfg` guards in downstream code.
2023-08-15 13:10:02 +04:00
John Nunley
61b921c466 Increase test coverage for generic modules 2023-08-15 13:10:02 +04:00
dAxpeDDa
794d0c1f73 On Web, use requestAnimationFrame for RedrawRequested 2023-08-15 13:10:02 +04:00
Kirill Chibisov
8ce58c7053 On Wayland, use frame callbacks to throttle RedrawRequested
Throttle RedrawRequested events by the frame callbacks, so the users
could render at the display refresh rate.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
cff9b01052 Add Window::on_present_notify to ack about drawing
That's a way to communicate to winit that you'll present to the window.
While it's a no-op for now, it'll be used to throttle drawing.
2023-08-15 13:10:02 +04:00
Diggory Hardy
7e9dc147d8 Export smol_str and impl Ord for Key
Fixes #2996.
2023-08-15 13:10:02 +04:00
Mads Marquart
d7827b36d3 Update icrate to v0.0.4 (#2992) 2023-08-15 13:10:02 +04:00
Marijn Suijten
5b90a4e194 On X11, remove the now-unrefrenced events.rs source file
#2662 renamed `VirtualKeyCode` to `Key` yet references to the former
type still exist in `src/platform_impl/linux/x11/events.rs`.  As it
turns out the `mod events;` in `x11/mod.rs` was removed in the same PR,
but the file accidentally stuck around without being referenced anywhere
else.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
281077a0d8 Remove lifetime from the Event
Lifetimes don't work nicely when dealing with multithreaded environments
in the current design of the existing winit's event handling model, so
remove it in favor of `InnerSizeWriter` fences passed to client, so they
could try to update the size.

Fixes #1387.
2023-08-15 13:10:02 +04:00
Tobias Hunger
d21395bb3f On Windows, keep window maximized when setting size bounds (#2899) 2023-08-15 13:10:02 +04:00
Géraud-Loup
f69616ac2c On Windows, add option to customize window class name (#2978) 2023-08-15 13:10:02 +04:00
Mads Marquart
645b1ff00f Update objc2 version (#2936)
* Upgrade to objc2 v0.4.0 and icrate v0.0.3

* Fix `touchBar` method

* Use ClassType::alloc

* Use #[method_id(...)] functionality in declare_class!
2023-08-15 13:10:02 +04:00
Robert Bragg
3925281652 Remove RedrawEventsCleared + MainEventsCleared, and added AboutToWait
The idea that redraw events are dispatched with a specific ordering
that makes it possible to specifically report when we have finished
dispatching redraw events isn't portable and the way in which we
dispatched RedrawEventsCleared was inconsistent across backends.

More generally speaking, there is no inherent relationship between
redrawing and event loop iterations. An event loop may wake up at any
frequency depending on what sources of input events are being listened
to but redrawing is generally throttled and in some way synchronized
with the display frequency.

Similarly there's no inherent relationship between a single event loop
iteration and the dispatching of any specific kind of "main" event.

An event loop wakes up when there are events to read (e.g. input
events or responses from a display server / compositor) and goes back
to waiting when there's nothing else to read.

There isn't really a special kind of "main" event that is dispatched
in order with respect to other events.

What we can do more portably is emit an event when the event loop
is about to block and wait for new events.

In practice this is very similar to how MainEventsCleared was
implemented except it wasn't the very last event previously since
redraw events could be dispatched afterwards.

The main backend where we don't strictly know when we're going to
wait for events is Web (since the real event loop is internal to
the browser). For now we emulate AboutToWait on Web similar to how
MainEventsCleared was dispatched.

In practice most applications almost certainly shouldn't care about
AboutToWait because the frequency of event loop iterations is
essentially arbitrary and usually irrelevant.
2023-08-15 13:10:02 +04:00
Robert Bragg
3bf0fa9ec8 Rename LoopDestroyed to LoopExiting
Considering the possibility of re-running an event loop via run_ondemand
then it's more correct to say that the loop is about to exit without
assuming it's going to be destroyed.
2023-08-15 13:10:02 +04:00
François
7de2bc7ae6 iOS: Always set timer when polling to avoid slow waking (#2979)
* Always set timer when polling to avoid slow waking

* add comment and changelog

---------

Co-authored-by: Dusty DeWeese <dustin.deweese@gmail.com>
Co-authored-by: Mads Marquart <mads@marquart.dk>
2023-08-15 13:10:02 +04:00
Robert Bragg
3f44eb1fd9 Add timeout argument to pump_events
This renames all internal implementations of pump_events_with_timeout
to pump_events and makes them public.

Since all platforms that support pump_events support timeouts there's
no need to have a separate API.
2023-08-15 13:10:02 +04:00
Robert Bragg
456c735bfe Windows: implement pump_events_with_timeout internally 2023-08-15 13:10:02 +04:00
Robert Bragg
973e6ad400 MacOS: implement pump_events_with_timeout internally
This layers pump_events on a pump_events_with_timeout API, like we have
for Linux and Android.

This is just an internal implementation detail for now but we could
consider making pump_events_with_timeout public, or just making it so
that pump_events() takes the timeout argument.
2023-08-15 13:10:02 +04:00
Robert Bragg
07652c76fb window_ondemand: wait for Destroyed event before exiting app
Considering the strict requirement that applications can't keep windows
across run_ondemand calls, this tries to make the window_ondemand example
explicitly wait for its Window to be destroyed before exiting each
run_ondemand iteration.

This updates the example to only `.set_exit()` after it gets a
`Destroyed` event after the Window has been dropped.

On Windows this works to ensure the Window is destroyed before the
example waits for 5 seconds.

Unfortunately though:
1. The Wayland backend doesn't emit `Destroyed` events for windows
2. The macOS backend emits `Destroyed` events before the window is
   really destroyed.

and so the example isn't currently portable.
2023-08-15 13:10:02 +04:00
Robert Bragg
7d93c34e42 Linux: Sync with server/compositor before exiting run_ondemand
Although we document that applications can't keep windows between
separate run_ondemand calls it's possible that the application has only
just dropped their windows and we need to flush these requests to the
server/compositor.

This fixes the window_ondemand example - by ensuring the window from
the first loop really is destroyed before waiting for 5 seconds
and starting the second loop.
2023-08-15 13:10:02 +04:00
Robert Bragg
a2e1a0ac19 Update CHANGELOG.md 2023-08-15 13:10:02 +04:00
Robert Bragg
f1a64b3155 Add examples/window_ondemand
A minimal example that shows an application running the event loop more
than once via `run_ondemand`

There is a 5 second delay between each run to help highlight problems
with destroying the window from the first loop.
2023-08-15 13:10:02 +04:00
Robert Bragg
f8ffa314d0 Add examples/window_pump_events
A minimal example of an application based on an external event loop that
calls `pump_events` for each iteration of the external loop.
2023-08-15 13:10:02 +04:00
Robert Bragg
e28974bc04 Re-work event loop run() API so it can return a Result
This re-works the portable `run()` API that consumes the `EventLoop` and
runs the loop on the calling thread until the app exits.

This can be supported across _all_ platforms and compared to the
previous `run() -> !` API is now able to return a `Result` status on all
platforms except iOS and Web. Fixes: #2709

By moving away from `run() -> !` we stop calling `std::process::exit()`
internally as a means to kill the process without returning which means
it's possible to return an exit status and applications can return from
their `main()` function normally.

This also fixes Android support where an Activity runs in a thread but
we can't assume to have full ownership of the process (other services
could be running in separate threads).

Additionally all examples have generally been updated so that `main()`
returns a `Result` from `run()`

Fixes: #2709
2023-08-15 13:10:02 +04:00
Robert Bragg
93f5f1ac3c Remove EventLoopExtRunReturn 2023-08-15 13:10:02 +04:00
Robert Bragg
a02c680a87 Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
Wayland:

I found the calloop abstraction a little awkward to work with while I was
trying to understand why there was surprising workaround code in the wayland
backend for manually dispatching pending events.

Investigating this further it looks like there may currently be several issues
with the calloop WaylandSource (with how prepare_read is used and with (not)
flushing writes before polling)

Considering the current minimal needs for polling in all winit backends I do
personally tend to think it would be simpler to just own the responsibility for
polling more directly, so the logic for wayland-client `prepare_read` wouldn't
be in a separate crate (and in this current situation would also be easier to fix)

I've tried to maintain the status quo with calloop + workarounds.

X11:

I found that the recent changes (4ac2006cbc) to port the X11 backend
from mio to calloop lost the ability to check for pending events before
needing to poll/dispatch. (The `has_pending` state being queried
before dispatching() was based on state that was filled in during
dispatching)

As part of the rebase this re-introduces the PeekableReceiver and
WakeSender which are small utilities on top of
`std::sync::mpsc::channel()`. This adds a calloop `PingSource`
so we can use a `Ping` as a generic event loop waker.

For taking into account false positive wake ups the X11 source now
tracks when the file descriptor is readable so after we poll via
calloop we can then specifically check if there are new X11 events
or pending redraw/user events when deciding whether to skip the
event loop iteration.
2023-08-15 13:10:02 +04:00
Robert Bragg
6bb62d0b13 MacOS: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
The implementation of `pump_events` essentially works by hooking into the
`RunLoopObserver` and requesting that the app should be stopped the next time
that the `RunLoop` prepares to wait for new events.

Originally I had thought I would poke the `CFRunLoop` for the app directly and
I was originally going to implement `pump_events` based on a timeout which I'd
seen SDL doing.

I found that `[NSApp run]` wasn't actually being stopped by asking the RunLoop
to stop directly and inferred that `NSApp run` will actually catch this and
re-start the loop.

Hooking into the observer and calling `[NSApp stop]` actually seems like a
better solution that doesn't need a hacky constant timeout.

The end result is quite similar to what happens with existing apps that
call `run_return` inside an external loop and cause the loop to exit for
each iteration (that also results in the `NSApp` stopping each
iteration).
2023-08-15 13:10:02 +04:00
Robert Bragg
fae4cbd2aa Windows: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand
A surprising amount of work was required to enable these extensions
on Windows.

I had originally assumed that pump_events was going to be very similar
to run except would use PeekMessageW instead of GetMessageW to avoid
blocking the external loop but I found the Windows backend broke
several assumptions I had.

Overall I think these changes can hopefully be considered a quite a
significant simplification (I think it's a net deletion of a fair amount
of code) and I think it also helps bring it into slightly closer alignment
with other backends too

Key changes:
- I have removed the `wait_thread` that was a fairly fiddly way of handling
  `ControlFlow::WaitUntil` timeouts in favor of using `SetTimer` which works
  with the same messages picked up by `GetMessage` and `PeekMessage`.
- I have removed the ordering guarantees between `MainEventsCleared`,
  `RedrawRequested` and `RedrawEventsCleared` events due to the complexity in
  maintaining this artificial ordering, which is already not supported
  consistently across backends anyway (in particular this ordering already
  isn't compatible with how MacOS / iOS work).
- `RedrawRequested` events are now directly dispatched via `WM_PAINT` messages
  - comparable to how `RedrawRequested` is dispatched via `drawRect` in the
  MacOS backend.
- I have re-worked how `NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`
  get dispatched to be more in line with the MacOS backend and also more in line
  with how we have recently discussed defining them for all platforms.

  `NewEvents` is conceptually delivered when the event loop "wakes up" and
  `MainEventsCleared` gets dispatched when the event loop is about to ask the
  OS to wait for new events.

  This is a more portable model, and is already how these events work in the
  MacOS backend.

  `RedrawEventsCleared` are just delivered after `MainEventsCleared` but this
  event no longer has a useful meaning.

Probably the most controversial thing here is that this "breaks" the ordering
rules for redraw event handling, but since my changes interacted with how the
order is maintained I was very reluctant to figure out how to continue
maintaining something that we have recently been discussing changing:

https://github.com/rust-windowing/winit/issues/2640.

Additionally, since the MacOS backend already doesn't strictly maintain this
order it's somewhat academic to see this as a breakage if Winit applications
can't really rely on it already.

This updates the documentation for `request_redraw()` to reflect that we
no longer guarantee that `RedrawRequested` events must be dispatched
after `MainEventsCleared`.
2023-08-15 13:10:02 +04:00
Robert Bragg
7a954c7e08 Android: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand 2023-08-15 13:10:02 +04:00
Robert Bragg
164dce2b8a Add EventLoopExtPumpEvents and EventLoopExtRunOnDemand
This adds two new extensions for running a Winit event loop which will
replace `EventLoopExtRunReturn`

The `run_return` API is trying to solve multiple problems and address
multiple, unrelated, use cases but in doing so it is not succeeding
at addressing any of them fully.

The notable use cases we have are:
1. Applications want to be able to implement their own external
   event loop and call some Winit API to poll / pump events, once
   per iteration of their own loop, without blocking the outer,
   external loop. Addressing #2706
2. Applications want to be able to re-run separate instantiations
   of some Winit-based GUI and want to allow the event loop to exit with
   a status, and then later be able to run the loop again for a new
   instantiation of their GUI. Addressing #2431

It's very notable that these use cases can't be supported across
all platforms and so they are extensions, similar to
`EventLoopExtRunReturn`

The intention is to support these extensions on:
- Windows
- Linux (X11 + Wayland)
- macOS
- Android

These extensions aren't compatible with Web or iOS though.

Each method of running the loop will behave consistently in terms of how
`NewEvents(Init)`, `Resumed` and `LoopDestroyed` events are dispatched
(so portable application code wouldn't necessarily need to have any awareness
of which method of running the loop was being used)

Once all backends have support for these extensions then we can
remove `EventLoopExtRunReturn`

For simplicity, the extensions are documented with the assumption that
the above platforms will be supported.

This patch makes no functional change, it only introduces these new
extensions so we can then handle adding platform-specific backends
in separate pull requests, so the work can be landed in stages.
2023-08-15 13:10:02 +04:00
daxpedda
0ba4283c29 Correctly detect that we don't support Emscripten (#2971) 2023-08-15 13:10:02 +04:00
John Nunley
62b4ba8b50 Replace libc with rustix in some modules
Unfortunately this isn't a total removal, for two reasons:

- We still need "libc" for the Xlib XIM implementation, for locales.
- BSD requires libc to check for main-threadedness.

First one we can likely resolve in the near future, not so sure about
the second one without using some weird pthreads trick.
2023-08-15 13:10:02 +04:00
Venceslas Duet
afebe2e7d1 On Windows, add drag_resize_window method support (#2966) 2023-08-15 13:10:02 +04:00
Kirill Chibisov
0efcfaf5a9 Add platform::startup_notify for Wayland/X11
The utils in this module should help the users to activate the windows
they create, as well as manage activation tokens environment variables.

The API is essential for Wayland in the first place, since some
compositors may decide initial focus of the window based on whether
the activation token was during the window creation.

Fixes #2279.

Co-authored-by: John Nunley <jtnunley01@gmail.com>
2023-08-15 13:10:02 +04:00
Venceslas Duet
d86ce9de9f On Wayland, fix Window::is_decorated with CSD 2023-08-15 13:10:02 +04:00
Kirill Chibisov
7fa7cea700 On Wayland, make the CSD frame double click reliable
It was discovered that on GNOME the click sometimes being swallowed
by the mutter's `wl_pointer::enter/leave` sequences. This was happening
due to `xdg_toplevel::move` making the pointer to leave the surface.

To make handling of that more robust, we could start the
`xdg_toplevel::move` when the actual pointer motion is being performed.

Links: https://github.com/alacritty/alacritty/issues/7011
Links: https://gitlab.gnome.org/GNOME/mutter/-/issues/2669#note_1790825
2023-08-15 13:10:02 +04:00
Kirill Chibisov
36ebad3246 On macOS, add a way to query amount of tabbed windows
This should provide a way to iterate all the tabs and select the last
tab. The tab indicies are now zero based as any other sane index.

Follow-up-to: c5941d105f (add tabbing API)
2023-08-15 13:10:02 +04:00
Sam
912c45e9f7 On macOS, set that we prefer tabbing
Winit now supports tabbing identifiers, thus set that we prefer tabbing,
in particular it'll make windows tab when using the same tabbing identifiers,
which is desirable for the end users.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
e2c71a4422 On macOS, move automatic tabbing setting to ELWT
Those are global for the application, so it's better to keep them
on EventLoopWindowTarget.
2023-08-15 13:10:02 +04:00
Kirill Chibisov
b50d9a0228 On macOS, add tabbing APIs
This should let the users control macOS tabbing and allow to create
windows in tab.

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
2023-08-15 13:10:02 +04:00
daxpedda
692f15c49f On Web, add WindowBuilderExtWebSys::with_append() (#2953) 2023-08-15 13:10:02 +04:00
daxpedda
5366694db2 Improve documentation of WindowBuilderExtWebSys methods (#2952) 2023-08-15 13:10:02 +04:00
John Nunley
7962271faa Replace parts of the Xlib backend with x11-rb 2023-08-15 13:10:02 +04:00
daxpedda
78b5f2feb8 On Web, remove Window::is_dark_mode() (#2951) 2023-08-15 13:10:02 +04:00
daxpedda
4baab2d93e Fix mentions of Wasm (#2950) 2023-08-15 13:10:02 +04:00
daxpedda
79385ecd1f On Web, implement and fix missing methods on Window(Builder) (#2949) 2023-08-15 13:10:02 +04:00
daxpedda
8d18043a3c Add Fullscreen API compatibility for Safari (#2948) 2023-08-15 13:10:02 +04:00
daxpedda
297c3f80eb On Web, cache commonly used values (#2947) 2023-08-15 13:10:02 +04:00
daxpedda
1d80005b91 Increase accuracy of various Web APIs (#2946) 2023-08-15 13:10:02 +04:00
daxpedda
fab0f62c5a Improve Web specific documentation for various APIs (#2941)
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2023-08-15 13:10:02 +04:00
Kirill Chibisov
d83188befd Rename Window::set_inner_size to Window::request_inner_size
Some systems could resize the window immediately and we'd rather
inform the users right away if that was the case, so they could
create e.g. EGLSurface without waiting for resize, which is really
important for Wayland.

Fixes #2868.
2023-08-15 13:10:02 +04:00
daxpedda
b9d89e97ed Fix touch location accuracy (#2944) 2023-08-15 13:10:02 +04:00
daxpedda
5fa4b8f003 On Web, implement WindowEvent::Occluded (#2940) 2023-08-15 13:10:02 +04:00
Imbris
7a4ce631bd On X11, avoid false positive key repeats
Instead of a single `bool` indicating that a key press has occured and
no key has been released since then, we store the scancode of the last
pressed key (if it is a key that repeats when held). This fixes a bug
where pressing a new key while one is already held down will be flagged
as a repeat even though it is obviously not a repeat.
2023-08-15 13:10:02 +04:00
Mads Marquart
8d5f82f0c0 Stop using &mut in Objective-C delegate methods (#2925)
* Make iOS declared classes not use &mut

* Prepare `init` methods for not having access to &mut self

* Prepare WinitWindow methods for not having access to &mut self

* Convert a bit of WinitView's to use interior mutability

* Convert a bit more of WinitView's to use interior mutability

* Convert the rest of WinitView to use interior mutability

* Use interior mutability instead of a Mutex for the CursorState

* Use interior mutability in WinitWindowDelegate
2023-08-15 13:10:02 +04:00
daxpedda
08fe32eac3 Don't unnecessarily clone canvas on Web (#2934) 2023-08-15 13:10:02 +04:00
daxpedda
1cddc96a0b Fix typos on Web (#2933) 2023-08-15 13:10:02 +04:00
Kirill Chibisov
84ef89eb1c Winit version 0.29.0-beta.0 2023-06-30 23:33:29 +04:00
211 changed files with 16726 additions and 12531 deletions

View File

@@ -6,146 +6,182 @@ on:
branches: [master]
jobs:
Check_Formatting:
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: hecrj/setup-rust-action@v1
- uses: dtolnay/rust-toolchain@stable
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check
run: cargo fmt -- --check
tests:
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.65.0']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.65.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.69.0'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
env:
# Set more verbose terminal output
CARGO_TERM_VERBOSE: true
RUST_BACKTRACE: 1
# Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings'
RUSTDOCFLAGS: '--deny=warnings'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: actions/checkout@v3
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
# the cache has been downloaded.
#
# This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v3
with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Cache cargo-apk
if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache
uses: actions/cache@v3
with:
path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
key: cargo-apk-v0-9-7
- uses: dtolnay/rust-toolchain@master
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
with:
toolchain: stable
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Check documentation
run: cargo doc --no-deps $OPTIONS --document-private-items
- name: Build crate
run: cargo $CMD build $OPTIONS
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.65.0'
run: cargo $CMD test $OPTIONS --features serde
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny:
name: cargo-deny
name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- aarch64-apple-ios
- aarch64-linux-android
- i686-pc-windows-gnu
- i686-pc-windows-msvc
- i686-unknown-linux-gnu
- wasm32-unknown-unknown
- x86_64-apple-darwin
- x86_64-apple-ios
- x86_64-pc-windows-gnu
- x86_64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-redox
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform }}
tests:
name: Tests
strategy:
fail-fast: false
matrix:
rust_version: ['1.64.0', stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
- { target: i686-pc-windows-msvc, os: windows-latest, }
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
- { target: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" }
- { target: x86_64-unknown-redox, os: ubuntu-latest, }
- { target: x86_64-apple-darwin, os: macos-latest, }
- { target: x86_64-apple-ios, os: macos-latest, }
- { target: aarch64-apple-ios, os: macos-latest, }
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
# doesn't currently work on Linux.
- { target: wasm32-unknown-unknown, os: windows-latest, }
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
CMD: ${{ matrix.platform.cmd }}
RUSTDOCFLAGS: -Dwarnings
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v3
# Used to cache cargo-web
- name: Cache cargo folder
uses: actions/cache@v1
with:
path: ~/.cargo
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk
- name: Check documentation
shell: bash
run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
- name: Build crate
shell: bash
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Build tests
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Run tests
shell: bash
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
- name: Lint with clippy
shell: bash
if: (matrix.rust_version == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
- name: Build tests with serde enabled
shell: bash
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
- name: Run tests with serde enabled
shell: bash
if: >
!contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.rust_version != '1.64.0'
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
arguments: --all-features --target ${{ matrix.platform.target }}

View File

@@ -2,20 +2,55 @@
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
Please keep one empty line before and after all headers. (This is required for
`git` to produce a conflict when a release is made while a PR is open and the
PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `# Unreleased` header.
And please only add new entries to the top of this list, right below the `#
Unreleased` header.
# Unreleased
- On Web, allow event loops to be recreated with `spawn`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- On Android, changed default behavior of Android to ignore volume keys letting the operating system handle them.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- On X11, fix `EventLoopWindowTarget::listen_device_events` effect being reversed.
# 0.29.2
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
- **Breaking:** remove `DeviceEvent::Text`.
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Remove all deprecated `modifiers` fields.
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- **Breaking:** `CursorIcon::Arrow` was removed.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- **Breaking:** Overhaul keyboard input handling.
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
@@ -25,11 +60,13 @@ And please only add new entries to the top of this list, right below the `# Unre
- Replace `VirtualKeyCode` with the `Key` enum.
- Replace `ScanCode` with the `KeyCode` enum.
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
- Add `KeyCode` to refer to keys (roughly) by their physical location.
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
understand.
- Add `Key` to represent the keys after they've been interpreted by the
active (software) keyboard layout.
- Add `NamedKey` to represent the categorized keys.
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
but can appear simultaneously in different spots on the same keyboard
@@ -38,56 +75,112 @@ And please only add new entries to the top of this list, right below the `# Unre
of dead key sequences.
- Add `KeyEventExtModifierSupplement` to expose additional (and less
portable) interpretations of a given key-press.
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
`KeyCode`.
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
`PhysicalKey`.
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
- On Orbital, fix `ModifiersChanged` not being sent.
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
- **Breaking:** `CursorIcon::Arrow` was removed.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix forward compatibility issues.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`.
- `WindowExtWindows::hwnd`.
- `WindowExtIOS::ui_window`.
- `WindowExtIOS::ui_view_controller`.
- `WindowExtIOS::ui_view`.
- `WindowExtMacOS::ns_window`.
- `WindowExtMacOS::ns_view`.
- `EventLoopWindowTargetExtWayland::wayland_display`.
- `WindowExtWayland::wayland_surface`.
- `WindowExtWayland::wayland_display`.
- `WindowExtX11::xlib_window`.
- `WindowExtX11::xlib_display`.
- `WindowExtX11::xlib_screen_id`.
- `WindowExtX11::xcb_connection`.
- Reexport `raw-window-handle` in `window` module.
- Add `ElementState::is_pressed`.
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
- Implement `PartialOrd` and `Ord` for `MouseButton`.
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
- **Breaking:** Bump MSRV from `1.60` to `1.64`.
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during
a transient activation.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
- On Windows, port to `windows-sys` version 0.48.0.
- On Web, fix pen treated as mouse input.
- On Web, send mouse position on button release as well.
- On Web, fix touch input not gaining or loosing focus.
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
- On Web, prevent clicks on the canvas to select text.
- Make `WindowBuilder` `Send + Sync`.
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- Make iOS windows usable from other threads.
- On Android, add force data to touch events.
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
- On Android, fix `DeviceId` to contain device id's.
- On Orbital, fix `ModifiersChanged` not being sent.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
- On Wayland, fix forward compatibility issues.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, make double clicking and moving the CSD frame more reliable.
- On Wayland, support `Occluded` event with xdg-shell v6
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
- On Web, `EventLoopProxy` now implements `Send`.
- On Web, `Window` now implements `Send` and `Sync`.
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
- On Web, use the correct canvas size when calculating the new size during scale factor change,
instead of using the output bitmap size.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On macOS, fix crash when dropping `Window`.
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to
the canvas size will be reported through `WindowEvent::Resized`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
- On Web, add Fullscreen API compatibility for Safari.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
- On Web, allow event loops to be recreated with `spawn`.
- On Web, enable event propagation.
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and
`DeviceEvent::Key` support.
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events
on Wayland, X11, Windows, macOS and Web.
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
- On Web, fix pen treated as mouse input.
- On Web, fix pointer button events not being processed when a buttons is already pressed.
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On Web, fix some `WindowBuilder` methods doing nothing.
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
- On Web, fix touch input not gaining or loosing focus.
- On Web, fix touch location to be as accurate as mouse position.
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`.
- On Web, implement `Window::set_(min|max)_inner_size()`.
- On Web, implement `WindowEvent::Occluded`.
- On Web, never return a `MonitorHandle`.
- On Web, prevent clicks on the canvas to select text.
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
- On Web, scale factor and dark mode detection are now more robust.
- On Web, send mouse position on button release as well.
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
- On Web: fix position of touch events to be relative to the canvas.
- On Windows, add `drag_resize_window` method support.
- On Windows, add horizontal MouseWheel `DeviceEvent`.
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
- On Windows, fix IME APIs not working when from non event loop thread.
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
- On Windows, port to `windows-sys` version 0.48.0.
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
- On X11, set `visual_id` in returned `raw-window-handle`.
- On iOS, add ability to change the status bar style.
- On iOS, add force data to touch events when using the Apple Pencil.
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
- On macOS, fix assertion when pressing `Globe` key.
- On macOS, fix crash in `window.set_minimized(false)`.
- On macOS, fix crash when dropping `Window`.
# 0.28.7
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
# 0.28.6

View File

@@ -20,7 +20,7 @@ your description of the issue as detailed as possible:
When making a code contribution to winit, before opening your pull request, please make sure that:
- your patch builds with Winit's minimal supported rust version - Rust 1.64.
- your patch builds with Winit's minimal supported rust version - Rust 1.65.
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.28.6"
version = "0.29.2"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
description = "Cross-platform window creation library."
edition = "2021"
@@ -10,10 +10,10 @@ readme = "README.md"
repository = "https://github.com/rust-windowing/winit"
documentation = "https://docs.rs/winit"
categories = ["gui"]
rust-version = "1.64.0"
rust-version = "1.65.0"
[package.metadata.docs.rs]
features = ["serde"]
features = ["rwh_04", "rwh_05", "rwh_06", "serde"]
default-target = "x86_64-unknown-linux-gnu"
# These are all tested in CI
targets = [
@@ -35,9 +35,9 @@ targets = [
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
@@ -45,6 +45,9 @@ wayland-csd-adwaita-notitle = ["sctk-adwaita"]
android-native-activity = ["android-activity/native-activity"]
android-game-activity = ["android-activity/game-activity"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
rwh_04 = ["dep:rwh_04", "ndk/rwh_04"]
rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases = "0.1.1"
@@ -55,30 +58,59 @@ cursor-icon = "1.0.0"
log = "0.4"
mint = { version = "0.5.6", optional = true }
once_cell = "1.12"
raw_window_handle = { package = "raw-window-handle", version = "0.5" }
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
serde = { version = "1", optional = true, features = ["serde_derive"] }
smol_str = "0.2.0"
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
simple_logger = { version = "2.1.0", default_features = false }
simple_logger = { version = "4.2.0", default_features = false }
winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = "0.3.0"
[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
android-activity = "0.4.0"
ndk = "0.7.0"
ndk-sys = "0.4.0"
android-activity = "0.5.0"
ndk = "0.8.0"
ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks`
objc2 = "0.4.1"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22.3"
dispatch = "0.2.0"
core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.0.4"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSAttributedString",
"Foundation_NSMutableAttributedString",
"Foundation_NSData",
"Foundation_NSDictionary",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.0.4"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSSet",
]
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
@@ -113,18 +145,22 @@ 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.3", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
fnv = { version = "1.0.3", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
wayland-client = { version = "0.30.0", optional = true }
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.7.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
@@ -134,6 +170,8 @@ redox_syscall = "0.3"
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'console',
'CssStyleDeclaration',
'Document',
@@ -145,8 +183,12 @@ features = [
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
@@ -155,6 +197,7 @@ features = [
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent'
]

View File

@@ -13,7 +13,9 @@ be used to create both games and applications. It supports the following main gr
- iOS
- Android
- Web
- via WASM
- Chrome
- Firefox
- Safari 13.1+
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features
@@ -117,6 +119,7 @@ If your PR makes notable changes to Winit's features, please update this section
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
@@ -143,14 +146,11 @@ If your PR makes notable changes to Winit's features, please update this section
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIWindow` object pointer
* Get the `UIViewController` object pointer
* Get the `UIView` object pointer
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility
* Status bar visibility and style
* Deferrring system gestures
* Getting the device idiom
* Getting the preferred video mode
@@ -172,7 +172,7 @@ Legend:
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
@@ -182,6 +182,7 @@ Legend:
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
@@ -192,20 +193,20 @@ Legend:
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ | |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
@@ -215,19 +216,19 @@ Legend:
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor | |❌ |✔️ | |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165

View File

@@ -6,14 +6,14 @@
```toml
[dependencies]
winit = "0.28.6"
winit = "0.29.2"
```
## [Documentation](https://docs.rs/winit)
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do.
## Contact Us
@@ -32,33 +32,6 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
```rust
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
```
Winit is only officially supported on the latest stable version of the Rust compiler.
### Cargo Features
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
@@ -67,6 +40,35 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## MSRV Policy
The Minimum Supported Rust Version (MSRV) of this crate is **1.65**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
formula:
```
min(sid, stable - 3)
```
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
`stable` is the latest stable version of Rust. This bound may be broken in the
event of a major ecosystem shift or a security vulnerability.
[Debian Sid]: https://packages.debian.org/sid/rustc
The exception to this is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction.
All crates in the [`rust-windowing`] organizations have the
same MSRV policy.
[`rust-windowing`]: https://github.com/rust-windowing
### Platform-specific usage
#### Wayland
@@ -95,7 +97,7 @@ book].
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
@@ -114,6 +116,7 @@ clash.
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29.2| `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
@@ -148,40 +151,13 @@ class. Your application _must_ specify the base class it needs via a feature fla
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For example, add this to Cargo.toml:
```toml
winit = { version = "0.28", features = [ "android-native-activity" ] }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11.0"
```
And, for example, define an entry point for your library like this:
```rust
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
let event_loop = EventLoopBuilder::with_user_event()
.with_android_app(app)
.build();
_main(event_loop);
}
```
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
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.28", features = [ "android-native-activity" ] }`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.2", 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 logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
@@ -192,13 +168,13 @@ doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::NewEvents(StartCause::Init)`.
`Event::Resumed`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI related code, see issue [#1705]. You should consider creating your windows
inside `Event::NewEvents(StartCause::Init)`.
inside `Event::Resumed`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238

View File

@@ -8,12 +8,12 @@ fn main() {
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
wasm_platform: { target_family = "wasm" },
wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform)) },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.

View File

@@ -4,4 +4,10 @@ disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
]

View File

@@ -31,16 +31,10 @@ multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
{ name = "memoffset"}, # due to different nix versions.
{ name = "memmap2" }, # sctk uses a different version until the next update
{ name = "libloading" }, # x11rb uses a different version until the next update
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
{ name = "num_enum"}, # See above ^, waiting for release
{ name = "num_enum_derive"},# See above ^, waiting for release
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
{ 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
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
]
skip-tree = []

View File

@@ -1,16 +1,23 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[path = "util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, macos_platform, windows_platform))]
fn main() {
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use raw_window_handle::HasRawWindowHandle;
use winit::{
dpi::{LogicalPosition, LogicalSize, Position},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
event_loop::{EventLoop, EventLoopWindowTarget},
raw_window_handle::HasRawWindowHandle,
window::{Window, WindowBuilder, WindowId},
};
@@ -19,7 +26,7 @@ fn main() {
event_loop: &EventLoopWindowTarget<()>,
windows: &mut HashMap<WindowId, Window>,
) {
let parent = parent.raw_window_handle();
let parent = parent.raw_window_handle().unwrap();
let mut builder = WindowBuilder::new()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
@@ -36,7 +43,7 @@ fn main() {
let mut windows = HashMap::new();
let event_loop: EventLoop<()> = EventLoop::new();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let parent_window = WindowBuilder::new()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
@@ -46,14 +53,12 @@ fn main() {
println!("parent window: {parent_window:?})");
event_loop.run(move |event: Event<'_, ()>, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event: Event<()>, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
windows.clear();
*control_flow = ControlFlow::Exit;
elwt.exit();
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
@@ -70,19 +75,23 @@ fn main() {
},
..
} => {
spawn_child_window(&parent_window, event_loop, &mut windows);
spawn_child_window(&parent_window, elwt, &mut windows);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
} else if let Event::RedrawRequested(wid) = event {
if let Some(window) = windows.get(&wid) {
fill::fill_window(window);
}
}
})
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
#[cfg(not(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
)))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows.");
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
}

View File

@@ -9,8 +9,8 @@ use web_time as time;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
window::WindowBuilder,
};
@@ -27,7 +27,7 @@ enum Mode {
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
println!("Press '1' to switch to Wait mode.");
@@ -36,7 +36,7 @@ fn main() {
println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.")
.build(&event_loop)
@@ -47,7 +47,7 @@ fn main() {
let mut wait_cancelled = false;
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
use winit::event::StartCause;
println!("{event:?}");
match event {
@@ -88,39 +88,41 @@ fn main() {
request_redraw = !request_redraw;
println!("\nrequest_redraw: {request_redraw}\n");
}
Key::Escape => {
Key::Named(NamedKey::Escape) => {
close_requested = true;
}
_ => (),
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::MainEventsCleared => {
Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw();
}
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
Event::RedrawEventsCleared => {
match mode {
Mode::Wait => control_flow.set_wait(),
Mode::Wait => elwt.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => {
if !wait_cancelled {
control_flow.set_wait_until(time::Instant::now() + WAIT_TIME);
elwt.set_control_flow(ControlFlow::WaitUntil(
time::Instant::now() + WAIT_TIME,
));
}
}
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
control_flow.set_poll();
elwt.set_control_flow(ControlFlow::Poll);
}
};
if close_requested {
elwt.exit();
}
}
_ => (),
}
});
})
}

View File

@@ -10,51 +10,44 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_title("A fantastic window!");
let mut cursor_idx = 0;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
window.set_cursor_icon(CURSORS[cursor_idx]);
if cursor_idx < CURSORS.len() - 1 {
cursor_idx += 1;
} else {
cursor_idx = 0;
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
WindowEvent::CloseRequested => {
elwt.exit();
}
_ => (),
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}
const CURSORS: &[CursorIcon] = &[

View File

@@ -4,16 +4,16 @@ use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
@@ -22,56 +22,52 @@ fn main() {
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
let result = match key {
Key::Escape => {
control_flow.set_exit();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
let result = match key {
Key::Named(NamedKey::Escape) => {
elwt.exit();
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
"a" => window.set_cursor_grab(CursorGrabMode::None),
"h" => {
window.set_cursor_visible(modifiers.shift_key());
Ok(())
}
Key::Character(ch) => match ch.to_lowercase().as_str() {
"g" => window.set_cursor_grab(CursorGrabMode::Confined),
"l" => window.set_cursor_grab(CursorGrabMode::Locked),
"a" => window.set_cursor_grab(CursorGrabMode::None),
"h" => {
window.set_cursor_visible(modifiers.shift_key());
Ok(())
}
_ => Ok(()),
},
_ => Ok(()),
};
},
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {err}");
}
if let Err(err) = result {
println!("error: {err}");
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
_ => (),
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
Event::RedrawRequested(_) => fill::fill_window(&window),
}
WindowEvent::ModifiersChanged(new) => modifiers = new.state(),
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
}
});
},
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {delta:?}"),
DeviceEvent::Button { button, state } => match state {
ElementState::Pressed => println!("mouse button {button} pressed"),
ElementState::Released => println!("mouse button {button} released"),
},
_ => (),
},
_ => (),
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
@@ -18,7 +18,9 @@ fn main() {
}
SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event().build();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
.build()
.unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -38,21 +40,20 @@ fn main() {
}
});
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
event_loop.run(move |event, elwt| match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
});
_ => (),
})
}
#[cfg(wasm_platform)]

View File

@@ -11,27 +11,25 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
event_loop.run(move |event, elwt| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position),
WindowEvent::MouseInput { state, button, .. } => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
@@ -40,7 +38,15 @@ fn main() {
&window_1
};
window.drag_window().unwrap()
match (button, state) {
(MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(),
(MouseButton::Right, ElementState::Released) => {
if let Some(position) = cursor_location {
window.show_window_menu(position);
}
}
_ => (),
}
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
@@ -54,22 +60,37 @@ fn main() {
..
},
..
} if c == "x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
} => match c.as_str() {
"x" => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
"d" => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.set_decorations(!window.is_decorated());
}
_ => (),
},
WindowEvent::RedrawRequested => {
if window_id == window_1.id() {
fill::fill_window(&window_1);
} else if window_id == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
},
Event::RedrawRequested(wid) => {
if wid == window_1.id() {
fill::fill_window(&window_1);
} else if wid == window_2.id() {
fill::fill_window(&window_2);
}
}
_ => (),
});
})
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {

View File

@@ -1,9 +1,10 @@
#![allow(clippy::single_match)]
use simple_logger::SimpleLogger;
use winit::dpi::PhysicalSize;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::keyboard::Key;
use winit::keyboard::{Key, NamedKey};
use winit::window::{Fullscreen, WindowBuilder};
#[cfg(target_os = "macos")]
@@ -12,12 +13,14 @@ use winit::platform::macos::WindowExtMacOS;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut decorations = true;
let mut minimized = false;
let mut with_min_size = false;
let mut with_max_size = false;
let window = WindowBuilder::new()
.with_title("Hello world!")
@@ -46,13 +49,13 @@ fn main() {
println!("- D\tToggle window decorations");
println!("- X\tMaximize window");
println!("- Z\tMinimize window");
println!("- I\tToggle mIn size limit");
println!("- A\tToggle mAx size limit");
event_loop.run(move |event, elwt, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
@@ -62,7 +65,7 @@ fn main() {
},
..
} => match key {
Key::Escape => control_flow.set_exit(),
Key::Named(NamedKey::Escape) => elwt.exit(),
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
Key::Character(ch) => match ch.to_lowercase().as_str() {
@@ -120,16 +123,41 @@ fn main() {
minimized = !minimized;
window.set_minimized(minimized);
}
"i" => {
with_min_size = !with_min_size;
let min_size = if with_min_size {
Some(PhysicalSize::new(100, 100))
} else {
None
};
window.set_min_inner_size(min_size);
eprintln!(
"Min: {with_min_size}: {min_size:?} => {:?}",
window.inner_size()
);
}
"a" => {
with_max_size = !with_max_size;
let max_size = if with_max_size {
Some(PhysicalSize::new(200, 200))
} else {
None
};
window.set_max_inner_size(max_size);
eprintln!(
"Max: {with_max_size}: {max_size:?} => {:?}",
window.inner_size()
);
}
_ => (),
},
_ => (),
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => {}
}
});
})
}

View File

@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Your faithful window")
@@ -22,70 +22,65 @@ fn main() {
let mut close_requested = false;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => {
// `CloseRequested` is sent when the close button on the window is pressed (or
// through whatever other mechanisms the window manager provides for closing a
// window). If you don't handle this event, the close button won't actually do
// anything.
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// A common thing to do here is prompt the user if they have unsaved work.
// Creating a proper dialog box for that is far beyond the scope of this
// example, so here we'll just respond to the Y and N keys.
println!("Are you ready to bid your window farewell? [Y/N]");
close_requested = true;
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("y") => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
control_flow.set_exit();
}
}
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
_ => (),
// In applications where you can safely close the window without further
// action from the user, this is generally where you'd handle cleanup before
// closing the window. How to close the window is detailed in the handler for
// the Y key.
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Released,
..
},
..
} => {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
match key.as_ref() {
Key::Character("y") => {
if close_requested {
// This is where you'll want to do any cleanup you need.
println!("Buh-bye!");
// For a single-window application like this, you'd normally just
// break out of the event loop here. If you wanted to keep running the
// event loop (i.e. if it's a multi-window application), you need to
// drop the window. That closes it, and results in `Destroyed` being
// sent.
elwt.exit();
}
}
Key::Character("n") => {
if close_requested {
println!("Your window will continue to stay by your side.");
close_requested = false;
}
}
_ => (),
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -5,15 +5,15 @@ use simple_logger::SimpleLogger;
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, Ime, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, KeyCode},
event_loop::EventLoop,
keyboard::NamedKey,
window::{ImePurpose, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new()
.with_level(LevelFilter::Trace)
.init()
@@ -24,7 +24,7 @@ fn main() {
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
println!("Press F3 to cycle through IME purposes.");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
@@ -39,71 +39,56 @@ fn main() {
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
cursor_position = position;
}
Event::WindowEvent {
event:
WindowEvent::MouseInput {
state: ElementState::Released,
..
},
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
}
Event::WindowEvent {
event: WindowEvent::Ime(event),
..
} => {
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
println!(
"Setting ime position to {}, {}",
cursor_position.x, cursor_position.y
);
ime_pos = cursor_position;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
}
Event::WindowEvent {
event: WindowEvent::KeyboardInput { event, .. },
..
} => {
println!("key: {event:?}");
WindowEvent::Ime(event) => {
println!("{event:?}");
may_show_ime = event != Ime::Disabled;
if may_show_ime {
window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10));
}
}
WindowEvent::KeyboardInput { event, .. } => {
println!("key: {event:?}");
if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F2 {
ime_allowed = !ime_allowed;
window.set_ime_allowed(ime_allowed);
println!("\nIME allowed: {ime_allowed}\n");
}
if event.state == ElementState::Pressed && event.logical_key == NamedKey::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
}
}
if event.state == ElementState::Pressed && event.logical_key == Key::F3 {
ime_purpose = match ime_purpose {
ImePurpose::Normal => ImePurpose::Password,
ImePurpose::Password => ImePurpose::Terminal,
_ => ImePurpose::Normal,
};
window.set_ime_purpose(ime_purpose);
println!("\nIME purpose: {ime_purpose:?}\n");
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -4,7 +4,7 @@
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
// WARNING: This is not available on all platforms (for example on the web).
platform::modifier_supplement::KeyEventExtModifierSupplement,
@@ -17,12 +17,12 @@ fn main() {
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
simple_logger::SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(400.0, 200.0))
@@ -31,12 +31,10 @@ fn main() {
let mut modifiers = ModifiersState::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ModifiersChanged(new) => {
modifiers = new.state();
}
@@ -54,12 +52,11 @@ fn main() {
}
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});
})
}

View File

@@ -7,7 +7,7 @@ use winit::{event_loop::EventLoop, window::WindowBuilder};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap();
if let Some(mon) = window.primary_monitor() {

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Mouse Wheel events")
@@ -34,12 +34,10 @@ In both cases the example window should move like the content of a scroll area i
In other words, the deltas indicate the direction in which to move the content (in this case the window)."
);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
println!("mouse wheel Line Delta: ({x},{y})");
@@ -57,12 +55,11 @@ In other words, the deltas indicate the direction in which to move the content (
window.set_outer_position(pos)
}
},
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use simple_logger::SimpleLogger;
@@ -9,7 +9,7 @@ fn main() {
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, ModifiersState},
keyboard::{Key, ModifiersState, NamedKey},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel},
};
@@ -17,7 +17,7 @@ fn main() {
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
for _ in 0..WINDOW_COUNT {
let window = WindowBuilder::new()
@@ -65,17 +65,17 @@ fn main() {
},
..
} => {
use Key::{ArrowLeft, ArrowRight};
use NamedKey::{ArrowLeft, ArrowRight};
window.set_title(&format!("{key:?}"));
let state = !modifiers.shift_key();
match key {
// Cycle through video modes
Key::ArrowRight | Key::ArrowLeft => {
video_mode_id = match key {
ArrowLeft => video_mode_id.saturating_sub(1),
ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1),
_ => unreachable!(),
};
Key::Named(ArrowRight) | Key::Named(ArrowLeft) => {
if key == ArrowLeft {
video_mode_id = video_mode_id.saturating_sub(1);
} else if key == ArrowRight {
video_mode_id = (video_modes.len() - 1).min(video_mode_id + 1);
}
println!("Picking video mode: {}", video_modes[video_mode_id]);
}
// WARNING: Consider using `key_without_modifers()` if available on your platform.
@@ -137,13 +137,15 @@ fn main() {
}),
"q" => window.request_redraw(),
"r" => window.set_resizable(state),
"s" => window.set_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
}),
"s" => {
let _ = window.request_inner_size(match state {
true => PhysicalSize::new(
WINDOW_SIZE.width + 100,
WINDOW_SIZE.height + 100,
),
false => WINDOW_SIZE,
});
}
"w" => {
if let Size::Physical(size) = WINDOW_SIZE.into() {
window
@@ -171,11 +173,10 @@ fn main() {
}
});
}
event_loop.run(move |event, _event_loop, control_flow| {
match !window_senders.is_empty() {
true => control_flow.set_wait(),
false => control_flow.set_exit(),
};
event_loop.run(move |event, elwt| {
if window_senders.is_empty() {
elwt.exit()
}
match event {
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested
@@ -184,7 +185,7 @@ fn main() {
event:
KeyEvent {
state: ElementState::Released,
logical_key: Key::Escape,
logical_key: Key::Named(NamedKey::Escape),
..
},
..
@@ -193,9 +194,7 @@ fn main() {
}
_ => {
if let Some(tx) = window_senders.get(&window_id) {
if let Some(event) = event.to_static() {
tx.send(event).unwrap();
}
tx.send(event).unwrap();
}
}
},

View File

@@ -4,18 +4,18 @@ use std::collections::HashMap;
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
keyboard::{Key, NamedKey},
window::Window,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut windows = HashMap::new();
for _ in 0..3 {
@@ -26,45 +26,39 @@ fn main() {
println!("Press N to open a new window.");
event_loop.run(move |event, event_loop, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
match event {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
control_flow.set_exit();
}
if windows.is_empty() {
elwt.exit();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Character(c),
..
},
is_synthetic: false,
..
} if matches!(c.as_ref(), "n" | "N") => {
let window = Window::new(event_loop).unwrap();
}
WindowEvent::KeyboardInput {
event,
is_synthetic: false,
..
} if event.state == ElementState::Pressed => match event.logical_key {
Key::Named(NamedKey::Escape) => elwt.exit(),
Key::Character(c) if c == "n" || c == "N" => {
let window = Window::new(elwt).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
}
_ => (),
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
Event::RedrawRequested(window_id) => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
})
}

View File

@@ -10,36 +10,33 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::MouseInput {
state: ElementState::Released,
..
} => {
window.request_redraw();
}
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
#[cfg(not(wasm_platform))]
fn main() {
fn main() -> Result<(), impl std::error::Error> {
use std::{sync::Arc, thread, time};
use simple_logger::SimpleLogger;
@@ -15,7 +15,7 @@ fn main() {
mod fill;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = {
let window = WindowBuilder::new()
@@ -33,23 +33,24 @@ fn main() {
}
});
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]

View File

@@ -5,16 +5,16 @@ use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
keyboard::{KeyCode, PhysicalKey},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let mut resizable = false;
@@ -27,16 +27,14 @@ fn main() {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: KeyCode::Space,
physical_key: PhysicalKey::Code(KeyCode::Space),
state: ElementState::Released,
..
},
@@ -46,12 +44,11 @@ fn main() {
println!("Resizable: {resizable}");
window.set_resizable(resizable);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
};
});
})
}

View File

@@ -0,0 +1,116 @@
//! Demonstrates the use of startup notifications on Linux.
#[cfg(any(x11_platform, wayland_platform))]
#[path = "./util/fill.rs"]
mod fill;
#[cfg(any(x11_platform, wayland_platform))]
mod example {
use std::collections::HashMap;
use std::rc::Rc;
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event_loop::EventLoop;
use winit::platform::startup_notify::{
EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify,
};
use winit::window::{Window, WindowBuilder, WindowId};
pub(super) fn main() -> Result<(), impl std::error::Error> {
// Create the event loop and get the activation token.
let event_loop = EventLoop::new().unwrap();
let mut current_token = match event_loop.read_token_from_env() {
Some(token) => Some(token),
None => {
println!("No startup notification token found in environment.");
None
}
};
let mut windows: HashMap<WindowId, Rc<Window>> = HashMap::new();
let mut counter = 0;
let mut create_first_window = false;
event_loop.run(move |event, elwt| {
match event {
Event::Resumed => create_first_window = true,
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
} => {
if logical_key == "n" {
if let Some(window) = windows.get(&window_id) {
// Request a new activation token on this window.
// Once we get it we will use it to create a window.
window
.request_activation_token()
.expect("Failed to request activation token.");
}
}
}
WindowEvent::CloseRequested => {
// Remove the window from the map.
windows.remove(&window_id);
if windows.is_empty() {
elwt.exit();
return;
}
}
WindowEvent::ActivationTokenDone { token, .. } => {
current_token = Some(token);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
super::fill::fill_window(window);
}
}
_ => {}
},
_ => (),
}
// See if we've passed the deadline.
if current_token.is_some() || create_first_window {
// Create the initial window.
let window = {
let mut builder =
WindowBuilder::new().with_title(format!("Window {}", counter));
if let Some(token) = current_token.take() {
println!("Creating a window with token {token:?}");
builder = builder.with_activation_token(token);
}
Rc::new(builder.build(elwt).unwrap())
};
// Add the window to the map.
windows.insert(window.id(), window.clone());
counter += 1;
create_first_window = false;
}
})
}
}
#[cfg(any(x11_platform, wayland_platform))]
fn main() -> Result<(), impl std::error::Error> {
example::main()
}
#[cfg(not(any(x11_platform, wayland_platform)))]
fn main() {
println!("This example is only supported on X11 and Wayland platforms.");
}

View File

@@ -3,7 +3,7 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::Key,
window::{Theme, WindowBuilder},
};
@@ -11,9 +11,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -27,53 +27,42 @@ fn main() {
println!(" (L) Light theme");
println!(" (D) Dark theme");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::WindowEvent {
event: WindowEvent::ThemeChanged(theme),
window_id,
..
} if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::ThemeChanged(theme) if window_id == window.id() => {
println!("Theme is changed: {theme:?}")
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("A" | "a") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(None);
}
Key::Character("L" | "l") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Light));
}
Key::Character("D" | "d") => {
println!("Theme was: {:?}", window.theme());
window.set_theme(Some(Theme::Dark));
}
_ => (),
},
WindowEvent::RedrawRequested => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("\nredrawing!\n");
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -9,16 +9,16 @@ use web_time::Instant;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::EventLoop,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -27,25 +27,28 @@ fn main() {
let timer_length = Duration::new(1, 0);
event_loop.run(move |event, _, control_flow| {
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::NewEvents(StartCause::Init) => {
control_flow.set_wait_until(Instant::now() + timer_length);
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
}
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
control_flow.set_wait_until(Instant::now() + timer_length);
elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length));
println!("\nTimer\n");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
} => elwt.exit(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -1,16 +1,16 @@
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("Touchpad gestures")
@@ -19,12 +19,10 @@ fn main() {
println!("Only supported on macOS at the moment.");
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::TouchpadMagnify { delta, .. } => {
if delta > 0.0 {
println!("Zoomed in {delta}");
@@ -42,10 +40,11 @@ fn main() {
println!("Rotated clockwise {delta}");
}
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_decorations(false)
@@ -22,19 +22,17 @@ fn main() {
window.set_title("A fantastic window!");
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
_ => (),
}
});
})
}

View File

@@ -9,7 +9,7 @@
use winit::window::Window;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))]
pub(super) fn fill_window(window: &Window) {
use softbuffer::{Context, Surface};
use std::cell::RefCell;
@@ -80,7 +80,7 @@ pub(super) fn fill_window(window: &Window) {
})
}
#[cfg(any(target_os = "android", target_os = "ios"))]
#[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))]
pub(super) fn fill_window(_window: &Window) {
// No-op on mobile platforms.
}

View File

@@ -5,7 +5,7 @@ use winit::event_loop::EventLoop;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let monitor = match event_loop.primary_monitor() {
Some(monitor) => monitor,
None => {

View File

@@ -3,24 +3,25 @@
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::KeyCode,
keyboard::Key,
window::{Fullscreen, WindowBuilder},
};
pub fn main() {
let event_loop = EventLoop::new();
pub fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let builder = WindowBuilder::new().with_title("A fantastic window!");
#[cfg(wasm_platform)]
let builder = {
use winit::platform::web::WindowBuilderExtWebSys;
builder.with_append(true)
};
let window = builder.build(&event_loop).unwrap();
#[cfg(wasm_platform)]
let log_list = wasm::insert_canvas_and_create_log_list(&window);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
#[cfg(wasm_platform)]
wasm::log_event(&log_list, &event);
@@ -28,8 +29,8 @@ pub fn main() {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
@@ -38,13 +39,13 @@ pub fn main() {
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: KeyCode::KeyF,
logical_key: Key::Character(c),
state: ElementState::Released,
..
},
..
},
} if window_id == window.id() => {
} if window_id == window.id() && c == "f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
@@ -53,37 +54,52 @@ pub fn main() {
}
_ => (),
}
});
})
}
#[cfg(wasm_platform)]
mod wasm {
use std::num::NonZeroU32;
use softbuffer::{Surface, SurfaceExtWeb};
use wasm_bindgen::prelude::*;
use winit::{event::Event, window::Window};
use winit::{
event::{Event, WindowEvent},
window::Window,
};
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
#[allow(clippy::main_recursion)]
super::main();
let _ = super::main();
}
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas().unwrap();
let mut surface = Surface::from_canvas(canvas.clone()).unwrap();
surface
.resize(
NonZeroU32::new(canvas.width()).unwrap(),
NonZeroU32::new(canvas.height()).unwrap(),
)
.unwrap();
let mut buffer = surface.buffer_mut().unwrap();
buffer.fill(0xFFF0000);
buffer.present().unwrap();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
// Set a background color for the canvas to make it easier to tell where the canvas is for debugging purposes.
canvas
.style()
.set_property("background-color", "crimson")
.unwrap();
body.append_child(&canvas).unwrap();
let style = &canvas.style();
style.set_property("margin", "50px").unwrap();
// Use to test interactions with border and padding.
//style.set_property("border", "50px solid black").unwrap();
//style.set_property("padding", "50px").unwrap();
let log_header = document.create_element("h2").unwrap();
log_header.set_text_content(Some("Event Log"));
@@ -100,11 +116,29 @@ mod wasm {
// Getting access to browser logs requires a lot of setup on mobile devices.
// So we implement this basic logging system into the page to give developers an easy alternative.
// As a bonus its also kind of handy on desktop.
if let Event::WindowEvent { event, .. } = &event {
let event = match event {
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => None,
Event::WindowEvent { event, .. } => Some(format!("{event:?}")),
Event::Resumed | Event::Suspended => Some(format!("{event:?}")),
_ => None,
};
if let Some(event) = event {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let log = document.create_element("li").unwrap();
log.set_text_content(Some(&format!("{event:?}")));
let date = js_sys::Date::new_0();
log.set_text_content(Some(&format!(
"{:02}:{:02}:{:02}.{:03}: {event}",
date.get_hours(),
date.get_minutes(),
date.get_seconds(),
date.get_milliseconds(),
)));
log_list
.insert_before(&log, log_list.first_child().as_ref())
.unwrap();

View File

@@ -12,7 +12,8 @@ mod wasm {
use winit::{
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
platform::web::WindowBuilderExtWebSys,
window::{Window, WindowBuilder},
};
@@ -30,13 +31,14 @@ This example demonstrates the desired future functionality which will possibly b
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
// When running in a non-wasm environment this would set the window size to 100x100.
// However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page.
.with_inner_size(PhysicalSize::new(100, 100))
.with_append(true)
.build(&event_loop)
.unwrap();
@@ -45,18 +47,14 @@ This example demonstrates the desired future functionality which will possibly b
// Render once with the size info we currently have
render_circle(&canvas, window.inner_size());
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
let _ = event_loop.run(move |event, _| match event {
Event::WindowEvent {
event: WindowEvent::Resized(resize),
window_id,
} if window_id == window.id() => {
render_circle(&canvas, resize);
}
_ => (),
});
}
@@ -72,7 +70,6 @@ This example demonstrates the desired future functionality which will possibly b
canvas
.style()
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");
body.append_child(&canvas).unwrap();
let explanation = document.create_element("pre").unwrap();
explanation.set_text_content(Some(EXPLANATION));

View File

@@ -10,9 +10,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -20,22 +20,24 @@ fn main() {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
println!("{event:?}");
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -14,9 +14,9 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -31,45 +31,38 @@ fn main() {
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { window_id, event } = event {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
..
} => match key.as_ref() {
Key::Character("F" | "f") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE);
}
Key::Character("G" | "g") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE);
}
Key::Character("H" | "h") => {
let buttons = window.enabled_buttons();
window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -7,16 +7,16 @@ use winit::{
dpi::{LogicalSize, PhysicalSize},
event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent},
event_loop::{DeviceEvents, EventLoop},
keyboard::{Key, KeyCode},
keyboard::{Key, KeyCode, PhysicalKey},
window::{Fullscreen, WindowBuilder},
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -38,9 +38,7 @@ fn main() {
event_loop.listen_device_events(DeviceEvents::Always);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
match event {
// This used to use the virtual key, but the new API
// only provides the `physical_key` (`Code`).
@@ -53,14 +51,14 @@ fn main() {
}),
..
} => match physical_key {
KeyCode::KeyM => {
PhysicalKey::Code(KeyCode::KeyM) => {
if minimized {
minimized = !minimized;
window.set_minimized(minimized);
window.focus_window();
}
}
KeyCode::KeyV => {
PhysicalKey::Code(KeyCode::KeyV) => {
if !visible {
visible = !visible;
window.set_visible(visible);
@@ -68,76 +66,72 @@ fn main() {
}
_ => (),
},
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"e" => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
Event::WindowEvent { window_id, event } => match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Character(key_str),
state: ElementState::Pressed,
..
},
..
} => match key_str.as_ref() {
// WARNING: Consider using `key_without_modifers()` if available on your platform.
// See the `key_binding` example
"e" => {
fn area(size: PhysicalSize<u32>) -> u32 {
size.width * size.height
}
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
let monitor = window.current_monitor().unwrap();
if let Some(mode) = monitor
.video_modes()
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
{
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
} else {
eprintln!("no video modes available");
}
}
}
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
"f" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
let monitor = window.current_monitor();
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
}
}
}
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
"p" => {
if window.fullscreen().is_some() {
window.set_fullscreen(None);
} else {
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
}
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
control_flow.set_exit();
}
"v" => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
"m" => {
minimized = !minimized;
window.set_minimized(minimized);
}
"q" => {
elwt.exit();
}
"v" => {
visible = !visible;
window.set_visible(visible);
}
"x" => {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
_ => (),
},
WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
})
}

View File

@@ -3,7 +3,7 @@
use simple_logger::SimpleLogger;
use winit::{
event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
event_loop::EventLoop,
keyboard::Key,
window::{CursorIcon, ResizeDirection, WindowBuilder},
};
@@ -13,9 +13,9 @@ const BORDER: f64 = 8.0;
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
@@ -27,12 +27,12 @@ fn main() {
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
event_loop.run(move |event, elwt| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
@@ -52,6 +52,8 @@ fn main() {
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
} else if !window.is_decorated() {
let _res = window.drag_window();
}
}
WindowEvent::KeyboardInput {
@@ -66,13 +68,14 @@ fn main() {
border = !border;
window.set_decorations(border);
}
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
});
})
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {

View File

@@ -4,7 +4,7 @@ use std::path::Path;
use simple_logger::SimpleLogger;
use winit::{
event::Event,
event::{Event, WindowEvent},
event_loop::EventLoop,
window::{Icon, WindowBuilder},
};
@@ -12,7 +12,7 @@ use winit::{
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
@@ -23,7 +23,7 @@ fn main() {
let icon = load_icon(Path::new(path));
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("An iconic window!")
@@ -33,22 +33,18 @@ fn main() {
.build(&event_loop)
.unwrap();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, .. } = event {
use winit::event::WindowEvent::*;
match event {
CloseRequested => control_flow.set_exit(),
DroppedFile(path) => {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::DroppedFile(path) => {
window.set_window_icon(Some(load_icon(&path)));
}
WindowEvent::RedrawRequested => fill::fill_window(&window),
_ => (),
}
} else if let Event::RedrawRequested(_) = event {
fill::fill_window(&window);
}
});
})
}
fn load_icon(path: &Path) -> Icon {

View File

@@ -0,0 +1,92 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
fn main() -> Result<(), impl std::error::Error> {
use std::time::Duration;
use simple_logger::SimpleLogger;
use winit::{
error::EventLoopError,
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_on_demand::EventLoopExtRunOnDemand,
window::{Window, WindowBuilder, WindowId},
};
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct App {
window_id: Option<WindowId>,
window: Option<Window>,
}
SimpleLogger::new().init().unwrap();
let mut event_loop = EventLoop::new().unwrap();
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> {
let mut app = App::default();
event_loop.run_on_demand(move |event, elwt| {
println!("Run {idx}: {:?}", event);
if let Some(window) = &app.window {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window.id() == window_id => {
println!("--------------------------------------------------------- Window {idx} CloseRequested");
app.window = None;
}
Event::AboutToWait => window.request_redraw(),
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
fill::fill_window(window);
}
_ => (),
}
} else if let Some(id) = app.window_id {
match event {
Event::WindowEvent {
event: WindowEvent::Destroyed,
window_id,
} if id == window_id => {
println!("--------------------------------------------------------- Window {idx} Destroyed");
app.window_id = None;
elwt.exit();
}
_ => (),
}
} else if let Event::Resumed = event {
let window = WindowBuilder::new()
.with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.build(elwt)
.unwrap();
app.window_id = Some(window.id());
app.window = Some(window);
}
})
}
run_app(&mut event_loop, 1)?;
println!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5));
let ret = run_app(&mut event_loop, 2);
println!("--------------------------------------------------------- Finished second loop");
ret
}
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
fn main() {
println!("This example is not supported on this platform");
}

View File

@@ -18,8 +18,8 @@ mod fill;
/// Prints the keyboard events characters received when option_is_alt is true versus false.
/// A left mouse click will toggle option_is_alt.
#[cfg(target_os = "macos")]
fn main() {
let event_loop = EventLoop::new();
fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -31,42 +31,39 @@ fn main() {
let mut option_as_alt = window.option_as_alt();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
event_loop.run(move |event, elwt| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent { event, .. } => match event {
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
option_as_alt = match option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::MainEventsCleared => {
window.request_redraw();
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
window.set_option_as_alt(option_as_alt);
}
Event::RedrawRequested(_) => {
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
});
_ => (),
})
}
#[cfg(not(target_os = "macos"))]

View File

@@ -7,23 +7,22 @@
x11_platform,
wayland_platform,
android_platform,
orbital_platform,
))]
fn main() {
use std::{thread::sleep, time::Duration};
fn main() -> std::process::ExitCode {
use std::{process::ExitCode, thread::sleep, time::Duration};
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::run_return::EventLoopExtRunReturn,
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
let mut event_loop = EventLoop::new();
let mut event_loop = EventLoop::new().unwrap();
SimpleLogger::new().init().unwrap();
let window = WindowBuilder::new()
@@ -31,12 +30,9 @@ fn main() {
.build(&event_loop)
.unwrap();
let mut quit = false;
while !quit {
event_loop.run_return(|event, _, control_flow| {
control_flow.set_wait();
'main: loop {
let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, elwt| {
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
@@ -45,27 +41,34 @@ fn main() {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
quit = true;
}
Event::MainEventsCleared => {
control_flow.set_exit();
}
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
_ => (),
}
});
if let PumpStatus::Exit(exit_code) = status {
break 'main ExitCode::from(exit_code as u8);
}
// Sleep for 1/60 second to simulate rendering
println!("rendering");
// Sleep for 1/60 second to simulate application work
//
// Since `pump_events` doesn't block it will be important to
// throttle the loop in the app somehow.
println!("Update()");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(ios_platform, wasm_platform))]
#[cfg(any(ios_platform, wasm_platform, orbital_platform))]
fn main() {
println!("This platform doesn't support run_return.");
println!("This platform doesn't support pump_events.");
}

View File

@@ -2,18 +2,18 @@ use log::debug;
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{ElementState, Event, KeyEvent, WindowEvent},
event::{ElementState, Event, WindowEvent},
event_loop::EventLoop,
keyboard::Key,
keyboard::NamedKey,
window::WindowBuilder,
};
#[path = "util/fill.rs"]
mod fill;
fn main() {
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
@@ -24,27 +24,13 @@ fn main() {
let mut has_increments = true;
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Space,
state: ElementState::Released,
..
},
..
},
window_id,
} if window_id == window.id() => {
event_loop.run(move |event, elwt| match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::KeyboardInput { event, .. }
if event.logical_key == NamedKey::Space
&& event.state == ElementState::Released =>
{
has_increments = !has_increments;
let new_increments = match window.resize_increments() {
@@ -54,11 +40,13 @@ fn main() {
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested(_) => {
WindowEvent::RedrawRequested => {
fill::fill_window(&window);
}
_ => (),
}
});
},
Event::AboutToWait => window.request_redraw(),
_ => (),
})
}

107
examples/window_tabbing.rs Normal file
View File

@@ -0,0 +1,107 @@
#![allow(clippy::single_match)]
#[cfg(target_os = "macos")]
use std::{collections::HashMap, num::NonZeroUsize};
#[cfg(target_os = "macos")]
use simple_logger::SimpleLogger;
#[cfg(target_os = "macos")]
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
event_loop::EventLoop,
keyboard::{Key, NamedKey},
platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS},
window::{Window, WindowBuilder},
};
#[cfg(target_os = "macos")]
#[path = "util/fill.rs"]
mod fill;
#[cfg(target_os = "macos")]
fn main() -> Result<(), impl std::error::Error> {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new().unwrap();
let mut windows = HashMap::new();
let window = Window::new(&event_loop).unwrap();
println!("Opened a new window: {:?}", window.id());
windows.insert(window.id(), window);
println!("Press N to open a new window.");
event_loop.run(move |event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
println!("Window {window_id:?} has received the signal to close");
// This drops the window, causing it to close.
windows.remove(&window_id);
if windows.is_empty() {
elwt.exit();
}
}
WindowEvent::Resized(_) => {
if let Some(window) = windows.get(&window_id) {
window.request_redraw();
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key,
..
},
is_synthetic: false,
..
} => match logical_key.as_ref() {
Key::Character("t") => {
let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier();
let window = WindowBuilder::new()
.with_tabbing_identifier(&tabbing_id)
.build(elwt)
.unwrap();
println!("Added a new tab: {:?}", window.id());
windows.insert(window.id(), window);
}
Key::Character("w") => {
let _ = windows.remove(&window_id);
}
Key::Named(NamedKey::ArrowRight) => {
windows.get(&window_id).unwrap().select_next_tab();
}
Key::Named(NamedKey::ArrowLeft) => {
windows.get(&window_id).unwrap().select_previous_tab();
}
Key::Character(ch) => {
if let Ok(index) = ch.parse::<NonZeroUsize>() {
let index = index.get();
// Select the last tab when pressing `9`.
let window = windows.get(&window_id).unwrap();
if index == 9 {
window.select_tab_at_index(window.num_tabs() - 1)
} else {
window.select_tab_at_index(index - 1);
}
}
}
_ => (),
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
}
}
})
}
#[cfg(not(target_os = "macos"))]
fn main() {
println!("This example is only supported on MacOS");
}

70
examples/x11_embed.rs Normal file
View File

@@ -0,0 +1,70 @@
//! A demonstration of embedding a winit window in an existing X11 application.
#[cfg(x11_platform)]
#[path = "util/fill.rs"]
mod fill;
#[cfg(x11_platform)]
mod imple {
use super::fill;
use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
platform::x11::WindowBuilderExtX11,
window::WindowBuilder,
};
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
// First argument should be a 32-bit X11 window ID.
let parent_window_id = std::env::args()
.nth(1)
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new()?;
let window = WindowBuilder::new()
.with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id)
.build(&event_loop)
.unwrap();
event_loop.run(move |event, elwt| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => elwt.exit(),
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
// Notify the windowing system that we'll be presenting to the window.
window.pre_present_notify();
fill::fill_window(&window);
}
_ => (),
}
})?;
Ok(())
}
}
#[cfg(not(x11_platform))]
mod imple {
pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
imple::entry()
}

View File

@@ -82,8 +82,7 @@
//! 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`.
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
//! integers (most often 1 or 2).
//! - **Wayland:** Scale factor is suggested by the the compositor.
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
//! information.
@@ -581,3 +580,421 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
Position::Logical(position.cast())
}
}
#[cfg(test)]
mod tests {
use crate::dpi;
use std::collections::HashSet;
macro_rules! test_pixel_int_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_eq!(
<$ty as Pixel>::from_f64(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::from_f64(37.9),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i32>(37),
37,
);
}
)*};
}
test_pixel_int_impl! {
test_pixel_int_u8 => u8,
test_pixel_int_u16 => u16,
test_pixel_int_u32 => u32,
test_pixel_int_i8 => i8,
test_pixel_int_i16 => i16
}
macro_rules! assert_approx_eq {
($a:expr, $b:expr $(,)?) => {
assert!(
($a - $b).abs() < 0.001,
"{} is not approximately equal to {}",
$a,
$b
);
};
}
macro_rules! test_pixel_float_impl {
($($name:ident => $ty:ty),*) => {$(
#[test]
fn $name() {
use dpi::Pixel;
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.0),
37.0,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.4),
37.4,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.5),
37.5,
);
assert_approx_eq!(
<$ty as Pixel>::from_f64(37.9),
37.9,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u16>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<u32>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i8>(37.5),
38,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.0),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.4),
37,
);
assert_eq!(
<$ty as Pixel>::cast::<i16>(37.5),
38,
);
}
)*};
}
test_pixel_float_impl! {
test_pixel_float_f32 => f32,
test_pixel_float_f64 => f64
}
#[test]
fn test_validate_scale_factor() {
assert!(dpi::validate_scale_factor(1.0));
assert!(dpi::validate_scale_factor(2.0));
assert!(dpi::validate_scale_factor(3.0));
assert!(dpi::validate_scale_factor(1.5));
assert!(dpi::validate_scale_factor(0.5));
assert!(!dpi::validate_scale_factor(0.0));
assert!(!dpi::validate_scale_factor(-1.0));
assert!(!dpi::validate_scale_factor(f64::INFINITY));
assert!(!dpi::validate_scale_factor(f64::NAN));
assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY));
}
#[test]
fn test_logical_position() {
let log_pos = dpi::LogicalPosition::new(1.0, 2.0);
assert_eq!(
log_pos.to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
log_pos.to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2));
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_pos,
dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalPosition::from((2.0, 2.0)),
dpi::LogicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalPosition::from([2.0, 3.0]),
dpi::LogicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = log_pos.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_pos.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_position() {
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::PhysicalPosition::from((2.0, 2.0)),
dpi::PhysicalPosition::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalPosition::from([2.0, 3.0]),
dpi::PhysicalPosition::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_logical_size() {
let log_size = dpi::LogicalSize::new(1.0, 2.0);
assert_eq!(
log_size.to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
log_size.to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2));
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_size,
dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0)
);
assert_eq!(
dpi::LogicalSize::from((2.0, 2.0)),
dpi::LogicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::LogicalSize::from([2.0, 3.0]),
dpi::LogicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = log_size.into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = log_size.into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_physical_size() {
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::PhysicalSize::from((2.0, 2.0)),
dpi::PhysicalSize::new(2.0, 2.0)
);
assert_eq!(
dpi::PhysicalSize::from([2.0, 3.0]),
dpi::PhysicalSize::new(2.0, 3.0)
);
let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into();
assert_eq!(x, [1.0, 2.0]);
}
#[test]
fn test_size() {
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)),
dpi::Size::Physical(dpi::PhysicalSize::new(1, 2))
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)),
dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0))
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalSize::new(0.5, 1.0)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalSize::new(1.0, 2.0)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalSize::new(1, 2)
);
assert_eq!(
dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalSize::new(2, 4)
);
let small = dpi::Size::Physical((1, 2).into());
let medium = dpi::Size::Logical((3, 4).into());
let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0));
let large = dpi::Size::Physical((5, 6).into());
assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical);
assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical);
}
#[test]
fn test_position() {
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)),
dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2))
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)),
dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0))
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0),
dpi::LogicalPosition::new(0.5, 1.0)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0),
dpi::LogicalPosition::new(1.0, 2.0)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0),
dpi::PhysicalPosition::new(1, 2)
);
assert_eq!(
dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0),
dpi::PhysicalPosition::new(2, 4)
);
}
// Eat coverage for the Debug impls et al
#[test]
fn ensure_attrs_do_not_panic() {
let _ = format!("{:?}", dpi::LogicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalPosition::<u32>::default());
let _ = format!("{:?}", dpi::LogicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::LogicalSize::<u32>::default());
let _ = format!("{:?}", dpi::PhysicalSize::<u32>::default().clone());
HashSet::new().insert(dpi::PhysicalSize::<u32>::default());
let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone());
}
}

View File

@@ -2,11 +2,14 @@ use std::{error, fmt};
use crate::platform_impl;
/// An error whose cause it outside Winit's control.
// TODO: Rename
/// An error that may be generated when requesting Winit state
#[derive(Debug)]
pub enum ExternalError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The operation was ignored.
Ignored,
/// The OS cannot perform the operation.
Os(OsError),
}
@@ -25,6 +28,27 @@ pub struct OsError {
error: platform_impl::OsError,
}
/// A general error that may occur while running the Winit event loop
#[derive(Debug)]
pub enum EventLoopError {
/// The operation is not supported by the backend.
NotSupported(NotSupportedError),
/// The OS cannot perform the operation.
Os(OsError),
/// The event loop can't be re-run while it's already running
AlreadyRunning,
/// The event loop can't be re-created.
RecreationAttempt,
/// Application has exit with an error status.
ExitFailure(i32),
}
impl From<OsError> for EventLoopError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
@@ -60,6 +84,7 @@ impl fmt::Display for ExternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
ExternalError::NotSupported(e) => e.fmt(f),
ExternalError::Ignored => write!(f, "Operation was ignored"),
ExternalError::Os(e) => e.fmt(f),
}
}
@@ -77,6 +102,41 @@ impl fmt::Display for NotSupportedError {
}
}
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
EventLoopError::AlreadyRunning => write!(f, "EventLoop is already running"),
EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
EventLoopError::NotSupported(e) => e.fmt(f),
EventLoopError::Os(e) => e.fmt(f),
EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
}
}
}
impl error::Error for OsError {}
impl error::Error for ExternalError {}
impl error::Error for NotSupportedError {}
impl error::Error for EventLoopError {}
#[cfg(test)]
mod tests {
#![allow(clippy::redundant_clone)]
use super::*;
// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
let _ = format!(
"{:?}, {}",
ExternalError::NotSupported(NotSupportedError::new()),
ExternalError::NotSupported(NotSupportedError::new())
);
}
}

View File

@@ -7,26 +7,24 @@
//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this:
//!
//! ```rust,ignore
//! let mut control_flow = ControlFlow::Poll;
//! let mut start_cause = StartCause::Init;
//!
//! while control_flow != ControlFlow::Exit {
//! event_handler(NewEvents(start_cause), ..., &mut control_flow);
//! while !elwt.exiting() {
//! event_handler(NewEvents(start_cause), elwt);
//!
//! for e in (window events, user events, device events) {
//! event_handler(e, ..., &mut control_flow);
//! event_handler(e, elwt);
//! }
//! event_handler(MainEventsCleared, ..., &mut control_flow);
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
//! event_handler(RedrawRequested(w), elwt);
//! }
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
//!
//! start_cause = wait_if_necessary(control_flow);
//! event_handler(AboutToWait, elwt);
//! start_cause = wait_if_necessary();
//! }
//!
//! event_handler(LoopDestroyed, ..., &mut control_flow);
//! event_handler(LoopExiting, elwt);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
@@ -34,39 +32,43 @@
//!
//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use smol_str::SmolStr;
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(wasm_platform))]
use std::time::Instant;
use smol_str::SmolStr;
#[cfg(wasm_platform)]
use web_time::Instant;
use crate::error::ExternalError;
#[cfg(doc)]
use crate::window::Window;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial,
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
platform_impl,
window::{Theme, WindowId},
window::{ActivationToken, Theme, WindowId},
};
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, PartialEq)]
pub enum Event<'a, T: 'static> {
#[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This event type is useful as a place to put code that should be done before you start
/// processing events, such as updating frame timing information for benchmarking or checking
/// the [`StartCause`][crate::event::StartCause] to see if a timer set by
/// the [`StartCause`] to see if a timer set by
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
NewEvents(StartCause),
/// Emitted when the OS sends an event to a winit window.
WindowEvent {
window_id: WindowId,
event: WindowEvent<'a>,
event: WindowEvent,
},
/// Emitted when the OS sends an event to a device.
@@ -84,7 +86,7 @@ pub enum Event<'a, T: 'static> {
///
/// Not all platforms support the notion of suspending applications, and there may be no
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
/// no formal application lifecycle (currently only Android and iOS do). For this reason,
/// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason,
/// Winit does not currently try to emit pseudo `Suspended` events before the application
/// quits on platforms without an application lifecycle.
///
@@ -129,7 +131,7 @@ pub enum Event<'a, T: 'static> {
///
/// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event
/// with the property [`persisted`] being true, which means that the page is being
/// put in the [´bfcache`] (back/forward cache) - an in-memory cache that stores a
/// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a
/// complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away.
///
@@ -195,7 +197,7 @@ pub enum Event<'a, T: 'static> {
///
/// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event
/// with the property [`persisted`] being true, which means that the page is being
/// restored from the [´bfcache`] (back/forward cache) - an in-memory cache that
/// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
/// stores a complete snapshot of a page (including the JavaScript heap) as the
/// user is navigating away.
///
@@ -206,119 +208,65 @@ pub enum Event<'a, T: 'static> {
/// [`Suspended`]: Self::Suspended
Resumed,
/// Emitted when all of the event loop's input events have been processed and redraw processing
/// is about to begin.
/// Emitted when the event loop is about to block and wait for new events.
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
/// Most applications shouldn't need to hook into this event since there is no real relationship
/// between how often the event loop needs to wake up and the dispatching of any specific events.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
/// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events.
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
///
///
/// ## Platform-specific
///
/// - **macOS / iOS:** Due to implementation difficulties, this will often, but not always, be
/// emitted directly inside `drawRect:`, with neither a preceding [`MainEventsCleared`] nor
/// subsequent `RedrawEventsCleared`. See [#2640] for work on this.
///
/// [`MainEventsCleared`]: Self::MainEventsCleared
/// [`RedrawEventsCleared`]: Self::RedrawEventsCleared
/// [#2640]: https://github.com/rust-windowing/winit/issues/2640
RedrawRequested(WindowId),
/// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
/// immediately after `MainEventsCleared`.
///
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
/// tasks have been completed.
///
/// [`RedrawRequested`]: Self::RedrawRequested
RedrawEventsCleared,
/// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`WindowEvent::RedrawRequested`] events.
AboutToWait,
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
/// gets emitted. You generally want to treat this as a "do on quit" event.
LoopDestroyed,
LoopExiting,
/// Emitted when the application has received a memory warning.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application
/// must [release memory] or risk being killed.
///
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
/// [release memory]: https://developer.android.com/topic/performance/memory#release
///
/// ### iOS
///
/// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`]
/// callback. The application must free as much memory as possible or risk being terminated, see
/// [how to respond to memory warnings].
///
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni
/// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings
///
/// ### Others
///
/// - **macOS / Wayland / Windows / Orbital:** Unsupported.
MemoryWarning,
}
impl<T: Clone> Clone for Event<'static, T> {
fn clone(&self) -> Self {
use self::Event::*;
match self {
WindowEvent { window_id, event } => WindowEvent {
window_id: *window_id,
event: event.clone(),
},
UserEvent(event) => UserEvent(event.clone()),
DeviceEvent { device_id, event } => DeviceEvent {
device_id: *device_id,
event: event.clone(),
},
NewEvents(cause) => NewEvents(*cause),
MainEventsCleared => MainEventsCleared,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopDestroyed => LoopDestroyed,
Suspended => Suspended,
Resumed => Resumed,
}
}
}
impl<'a, T> Event<'a, T> {
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
MainEventsCleared => Ok(MainEventsCleared),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
LoopDestroyed => Ok(LoopDestroyed),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
}
}
/// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime.
/// Otherwise, return `None`.
pub fn to_static(self) -> Option<Event<'static, T>> {
use self::Event::*;
match self {
WindowEvent { window_id, event } => event
.to_static()
.map(|event| WindowEvent { window_id, event }),
UserEvent(event) => Some(UserEvent(event)),
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
RedrawRequested(wid) => Some(RedrawRequested(wid)),
RedrawEventsCleared => Some(RedrawEventsCleared),
LoopDestroyed => Some(LoopDestroyed),
Suspended => Some(Suspended),
Resumed => Some(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
}
@@ -354,8 +302,22 @@ pub enum StartCause {
}
/// Describes an event from a [`Window`].
#[derive(Debug, PartialEq)]
pub enum WindowEvent<'a> {
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
///
#[cfg_attr(
not(any(x11_platform, wayland_platfrom)),
allow(rustdoc::broken_intra_doc_links)
)]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone {
serial: AsyncRequestSerial,
token: ActivationToken,
},
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>),
@@ -430,6 +392,14 @@ pub enum WindowEvent<'a> {
Ime(Ime),
/// The cursor has moved on the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorMoved {
device_id: DeviceId,
@@ -440,9 +410,25 @@ pub enum WindowEvent<'a> {
},
/// The cursor has entered the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorEntered { device_id: DeviceId },
/// The cursor has left the window.
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred.
@@ -528,7 +514,12 @@ pub enum WindowEvent<'a> {
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **macOS:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
Touch(Touch),
/// The window's scale factor has changed.
@@ -546,7 +537,10 @@ pub enum WindowEvent<'a> {
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
ScaleFactorChanged {
scale_factor: f64,
new_inner_size: &'a mut PhysicalSize<u32>,
/// Handle to update inner size during scale changes.
///
/// See [`InnerSizeWriter`] docs for more details.
inner_size_writer: InnerSizeWriter,
},
/// The system window theme has changed.
@@ -564,207 +558,39 @@ pub enum WindowEvent<'a> {
/// This is different to window visibility as it depends on whether the window is closed,
/// minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific behavior:
/// - **iOS / Android / Web / Wayland / Windows / Orbital:** Unsupported.
/// ## Platform-specific
///
/// ### iOS
///
/// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`]
/// callback which means the application should start preparing its data. The `Occluded(true)` event is
/// emitted in response to an [`applicationDidEnterBackground`] callback which means the application
/// should free resources (according to the [iOS application lifecycle]).
///
/// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
/// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// ### Others
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **Android / Windows / Orbital:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
Occluded(bool),
}
impl Clone for WindowEvent<'static> {
fn clone(&self) -> Self {
use self::WindowEvent::*;
return match self {
Resized(size) => Resized(*size),
Moved(pos) => Moved(*pos),
CloseRequested => CloseRequested,
Destroyed => Destroyed,
DroppedFile(file) => DroppedFile(file.clone()),
HoveredFile(file) => HoveredFile(file.clone()),
HoveredFileCancelled => HoveredFileCancelled,
Focused(f) => Focused(*f),
KeyboardInput {
device_id,
event,
is_synthetic,
} => KeyboardInput {
device_id: *device_id,
event: event.clone(),
is_synthetic: *is_synthetic,
},
Ime(preedit_state) => Ime(preedit_state.clone()),
ModifiersChanged(modifiers) => ModifiersChanged(*modifiers),
CursorMoved {
device_id,
position,
} => CursorMoved {
device_id: *device_id,
position: *position,
},
CursorEntered { device_id } => CursorEntered {
device_id: *device_id,
},
CursorLeft { device_id } => CursorLeft {
device_id: *device_id,
},
MouseWheel {
device_id,
delta,
phase,
} => MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
MouseInput {
device_id,
state,
button,
} => MouseInput {
device_id: *device_id,
state: *state,
button: *button,
},
TouchpadMagnify {
device_id,
delta,
phase,
} => TouchpadMagnify {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
SmartMagnify { device_id } => SmartMagnify {
device_id: *device_id,
},
TouchpadRotate {
device_id,
delta,
phase,
} => TouchpadRotate {
device_id: *device_id,
delta: *delta,
phase: *phase,
},
TouchpadPressure {
device_id,
pressure,
stage,
} => TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage,
},
AxisMotion {
device_id,
axis,
value,
} => AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value,
},
Touch(touch) => Touch(*touch),
ThemeChanged(theme) => ThemeChanged(*theme),
ScaleFactorChanged { .. } => {
unreachable!("Static event can't be about scale factor changing")
}
Occluded(occluded) => Occluded(*occluded),
};
}
}
impl<'a> WindowEvent<'a> {
pub fn to_static(self) -> Option<WindowEvent<'static>> {
use self::WindowEvent::*;
match self {
Resized(size) => Some(Resized(size)),
Moved(position) => Some(Moved(position)),
CloseRequested => Some(CloseRequested),
Destroyed => Some(Destroyed),
DroppedFile(file) => Some(DroppedFile(file)),
HoveredFile(file) => Some(HoveredFile(file)),
HoveredFileCancelled => Some(HoveredFileCancelled),
Focused(focused) => Some(Focused(focused)),
KeyboardInput {
device_id,
event,
is_synthetic,
} => Some(KeyboardInput {
device_id,
event,
is_synthetic,
}),
ModifiersChanged(modifers) => Some(ModifiersChanged(modifers)),
Ime(event) => Some(Ime(event)),
CursorMoved {
device_id,
position,
} => Some(CursorMoved {
device_id,
position,
}),
CursorEntered { device_id } => Some(CursorEntered { device_id }),
CursorLeft { device_id } => Some(CursorLeft { device_id }),
MouseWheel {
device_id,
delta,
phase,
} => Some(MouseWheel {
device_id,
delta,
phase,
}),
MouseInput {
device_id,
state,
button,
} => Some(MouseInput {
device_id,
state,
button,
}),
TouchpadMagnify {
device_id,
delta,
phase,
} => Some(TouchpadMagnify {
device_id,
delta,
phase,
}),
SmartMagnify { device_id } => Some(SmartMagnify { device_id }),
TouchpadRotate {
device_id,
delta,
phase,
} => Some(TouchpadRotate {
device_id,
delta,
phase,
}),
TouchpadPressure {
device_id,
pressure,
stage,
} => Some(TouchpadPressure {
device_id,
pressure,
stage,
}),
AxisMotion {
device_id,
axis,
value,
} => Some(AxisMotion {
device_id,
axis,
value,
}),
Touch(touch) => Some(Touch(touch)),
ThemeChanged(theme) => Some(ThemeChanged(theme)),
ScaleFactorChanged { .. } => None,
Occluded(occluded) => Some(Occluded(occluded)),
}
}
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
RedrawRequested,
}
/// Identifier of an input device.
@@ -786,7 +612,8 @@ impl DeviceId {
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
}
}
@@ -832,10 +659,6 @@ pub enum DeviceEvent {
},
Key(RawKeyEvent),
Text {
codepoint: char,
},
}
/// Describes a keyboard input as a raw device event.
@@ -848,7 +671,7 @@ pub enum DeviceEvent {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RawKeyEvent {
pub physical_key: keyboard::KeyCode,
pub physical_key: keyboard::PhysicalKey,
pub state: ElementState,
}
@@ -880,12 +703,12 @@ pub struct KeyEvent {
/// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys
/// are usually handled at the hardware or OS level, and aren't surfaced to applications. If
/// you somehow see this in the wild, we'd like to know :)
pub physical_key: keyboard::KeyCode,
pub physical_key: keyboard::PhysicalKey,
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
#[cfg_attr(
not(any(target_os = "macos", target_os = "windows", target_os = "linux")),
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
@@ -920,7 +743,7 @@ pub struct KeyEvent {
/// An additional difference from `logical_key` is that
/// this field stores the text representation of any key
/// that has such a representation. For example when
/// `logical_key` is `Key::Enter`, this field is `Some("\r")`.
/// `logical_key` is `Key::Named(NamedKey::Enter)`, this field is `Some("\r")`.
///
/// This is `None` if the current keypress cannot
/// be interpreted as text.
@@ -1137,7 +960,12 @@ pub enum TouchPhase {
///
/// ## Platform-specific
///
/// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`].
/// - **macOS:** Unsupported.
///
/// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
/// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
/// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
@@ -1148,7 +976,10 @@ pub struct Touch {
///
/// ## Platform-specific
///
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
/// - 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).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1225,13 +1056,20 @@ pub enum ElementState {
Released,
}
impl ElementState {
/// True if `self == Pressed`.
pub fn is_pressed(self) -> bool {
self == ElementState::Pressed
}
}
/// Describes a button of a mouse controller.
///
/// ## Platform-specific
///
/// **macOS:** `Back` and `Forward` might not work with all hardware.
/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
@@ -1268,3 +1106,237 @@ pub enum MouseScrollDelta {
/// and move the content right and down (to reveal more things left and up).
PixelDelta(PhysicalPosition<f64>),
}
/// Handle to synchroniously change the size of the window from the
/// [`WindowEvent`].
#[derive(Debug, Clone)]
pub struct InnerSizeWriter {
pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
}
impl InnerSizeWriter {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_inner_size }
}
/// Try to request inner size which will be set synchroniously on the window.
pub fn request_inner_size(
&mut self,
new_inner_size: PhysicalSize<u32>,
) -> Result<(), ExternalError> {
if let Some(inner) = self.new_inner_size.upgrade() {
*inner.lock().unwrap() = new_inner_size;
Ok(())
} else {
Err(ExternalError::Ignored)
}
}
}
impl PartialEq for InnerSizeWriter {
fn eq(&self, other: &Self) -> bool {
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
}
}
#[cfg(test)]
mod tests {
use crate::event;
use std::collections::{BTreeSet, HashSet};
macro_rules! foreach_event {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
#[allow(deprecated)]
{
use crate::event::{Event::*, Ime::Enabled, WindowEvent::*};
use crate::window::WindowId;
// Mainline events.
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
x(Resumed);
// Window events.
let with_window_event = |wev| {
x(WindowEvent {
window_id: wid,
event: wev,
})
};
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(Resized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(CursorMoved {
device_id: did,
position: (0, 0).into(),
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did });
with_window_event(MouseWheel {
device_id: did,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(MouseInput {
device_id: did,
state: event::ElementState::Pressed,
button: event::MouseButton::Other(0),
});
with_window_event(TouchpadMagnify {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(SmartMagnify { device_id: did });
with_window_event(TouchpadRotate {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure {
device_id: did,
pressure: 0.0,
stage: 0,
});
with_window_event(AxisMotion {
device_id: did,
axis: 0,
value: 0.0,
});
with_window_event(Touch(event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}));
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event = |dev_ev| {
x(event::Event::DeviceEvent {
device_id: did,
event: dev_ev,
})
};
with_device_event(Added);
with_device_event(Removed);
with_device_event(MouseMotion {
delta: (0.0, 0.0).into(),
});
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Motion {
axis: 0,
value: 0.0,
});
with_device_event(Button {
button: 0,
state: event::ElementState::Pressed,
});
}
}};
}
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event<()>| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
assert_eq!(force.normalized(), 0.0);
let force2 = event::Force::Calibrated {
force: 5.0,
max_possible_force: 2.5,
altitude_angle: None,
};
assert_eq!(force2.normalized(), 2.0);
let force3 = event::Force::Calibrated {
force: 5.0,
max_possible_force: 2.5,
altitude_angle: Some(std::f64::consts::PI / 2.0),
};
assert_eq!(force3.normalized(), 2.0);
}
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| {
let _ = format!("{:?}", event);
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();
let mut set2 = BTreeSet::new();
set2.insert(did);
set2.insert(did);
HashSet::new().insert(event::TouchPhase::Started.clone());
HashSet::new().insert(event::MouseButton::Left.clone());
HashSet::new().insert(event::Ime::Enabled);
let _ = event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}
.clone();
let _ = event::Force::Calibrated {
force: 0.0,
max_possible_force: 0.0,
altitude_angle: None,
}
.clone();
}
}

View File

@@ -9,15 +9,17 @@
//! handle events.
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::{error, fmt};
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
#[cfg(not(wasm_platform))]
use std::time::{Duration, Instant};
#[cfg(wasm_platform)]
use web_time::{Duration, Instant};
use crate::error::EventLoopError;
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to
@@ -87,23 +89,23 @@ impl<T> EventLoopBuilder<T> {
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Attempting to create the event loop on a different thread, or multiple event loops in
/// the same application, will panic. This restriction isn't
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
/// any thread.
///
/// Calling this function will result in display backend initialisation.
///
/// ## Panics
///
/// Attempting to create the event loop off the main thread will panic. This
/// restriction isn't strictly necessary on all platforms, but is imposed to
/// eliminate any nasty surprises when porting to platforms that require it.
/// `EventLoopBuilderExt::any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event
/// loop on any thread.
///
/// ## Platform-specific
///
/// - **Linux:** Backend type can be controlled using an environment variable
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
/// will fall back on X11. If this variable is set with any other value, winit will panic.
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`.
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY`
/// or `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
///
/// [`platform`]: crate::platform
#[cfg_attr(
@@ -115,17 +117,17 @@ impl<T> EventLoopBuilder<T> {
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> EventLoop<T> {
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
panic!("Creating EventLoop multiple times is not supported.");
return Err(EventLoopError::RecreationAttempt);
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
Ok(EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
_marker: PhantomData,
}
})
}
#[cfg(wasm_platform)]
@@ -146,28 +148,21 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
}
}
/// Set by the user callback given to the [`EventLoop::run`] method.
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Poll`].
/// Defaults to [`Wait`].
///
/// ## Persistency
///
/// Almost every change is persistent between multiple calls to the event loop closure within a
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
/// reset the control flow to [`Poll`].
///
/// [`ExitWithCode`]: Self::ExitWithCode
/// [`Poll`]: Self::Poll
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// [`Wait`]: Self::Wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
@@ -179,88 +174,22 @@ pub enum ControlFlow {
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
///
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
/// with exit code 0.
///
/// ## Platform-specific
///
/// - **Android / iOS / WASM:** The supplied exit code is unused.
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
/// [`std::process::exit`].
///
/// [`LoopDestroyed`]: Event::LoopDestroyed
/// [`Exit`]: ControlFlow::Exit
ExitWithCode(i32),
}
impl ControlFlow {
/// Alias for [`ExitWithCode`]`(0)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
#[allow(non_upper_case_globals)]
pub const Exit: Self = Self::ExitWithCode(0);
/// Sets this to [`Poll`].
///
/// [`Poll`]: Self::Poll
pub fn set_poll(&mut self) {
*self = Self::Poll;
}
/// Sets this to [`Wait`].
///
/// [`Wait`]: Self::Wait
pub fn set_wait(&mut self) {
*self = Self::Wait;
}
/// Sets this to [`WaitUntil`]`(instant)`.
///
/// [`WaitUntil`]: Self::WaitUntil
pub fn set_wait_until(&mut self, instant: Instant) {
*self = Self::WaitUntil(instant);
}
/// Sets this to wait until a timeout has expired.
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn set_wait_timeout(&mut self, timeout: Duration) {
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => self.set_wait_until(instant),
None => self.set_wait(),
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
/// Sets this to [`ExitWithCode`]`(code)`.
///
/// [`ExitWithCode`]: Self::ExitWithCode
pub fn set_exit_with_code(&mut self, code: i32) {
*self = Self::ExitWithCode(code);
}
/// Sets this to [`Exit`].
///
/// [`Exit`]: Self::Exit
pub fn set_exit(&mut self) {
*self = Self::Exit;
}
}
impl Default for ControlFlow {
#[inline(always)]
fn default() -> Self {
Self::Poll
}
}
impl EventLoop<()> {
@@ -268,42 +197,46 @@ impl EventLoop<()> {
///
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
#[inline]
pub fn new() -> EventLoop<()> {
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
EventLoopBuilder::new().build()
}
}
impl Default for EventLoop<()> {
fn default() -> Self {
Self::new()
}
}
impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> EventLoop<T> {
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
EventLoopBuilder::<T>::with_user_event().build()
}
/// Hijacks the calling thread and initializes the winit event loop with the provided
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any pending events.
///
/// Since the closure is `'static`, it must be a `move` closure if it needs to
/// access any data from the calling context.
///
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
/// event loop's behavior.
///
/// Any values not passed to this function will *not* be dropped.
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
/// disconnects.
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped.
///
/// [`ControlFlow`]: crate::event_loop::ControlFlow
/// Web applications are recommended to use `spawn()` instead of `run()` to avoid the need
/// for the Javascript exception trick, and to make it clearer that the event loop runs
/// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms.
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
#[inline]
pub fn run<F>(self, event_handler: F) -> !
#[cfg(not(all(wasm_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &EventLoopWindowTarget<T>),
{
self.event_loop.run(event_handler)
}
@@ -316,10 +249,46 @@ impl<T> EventLoop<T> {
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.event_loop.window_target().p.raw_display_handle()
#[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(&**self)
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
@@ -347,7 +316,7 @@ impl<T> EventLoopWindowTarget<T> {
///
/// ## Platform-specific
///
/// **Wayland:** Always returns `None`.
/// **Wayland / Web:** Always returns `None`.
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.p
@@ -366,16 +335,49 @@ impl<T> EventLoopWindowTarget<T> {
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
#[cfg(any(x11_platform, wasm_platform, wayland_platform, windows))]
self.p.listen_device_events(_allowed);
pub fn listen_device_events(&self, allowed: DeviceEvents) {
self.p.listen_device_events(allowed);
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
}
/// Gets the current [`ControlFlow`].
pub fn control_flow(&self) -> ControlFlow {
self.p.control_flow()
}
/// This exits the event loop.
///
/// See [`LoopExiting`](Event::LoopExiting).
pub fn exit(&self) {
self.p.exit()
}
/// Returns if the [`EventLoop`] is about to stop.
///
/// See [`exit()`](Self::exit).
pub fn exiting(&self) -> bool {
self.p.exiting()
}
}
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> RawDisplayHandle {
self.p.raw_display_handle()
#[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.p.raw_display_handle_rwh_06()?;
// SAFETY: The display will never be deallocated while the event loop is alive.
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
self.p.raw_display_handle_rwh_05()
}
}
@@ -437,3 +439,29 @@ pub enum DeviceEvents {
/// Never capture device events.
Never,
}
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarious could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsyncRequestSerial {
serial: u64,
}
impl AsyncRequestSerial {
// TODO(kchibisov) remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: we rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

View File

@@ -69,7 +69,7 @@
//
// --------- END OF W3C SHORT NOTICE ---------------------------------------------------------------
use smol_str::SmolStr;
pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier
///
@@ -82,7 +82,7 @@ use smol_str::SmolStr;
///
/// - Correctly match key press and release events.
/// - On non-web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKeyCode {
Unidentified,
@@ -135,7 +135,7 @@ impl std::fmt::Debug for NativeKeyCode {
/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKey {
Unidentified,
@@ -185,26 +185,112 @@ impl std::fmt::Debug for NativeKey {
}
}
impl From<NativeKeyCode> for NativeKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
match code {
NativeKeyCode::Unidentified => NativeKey::Unidentified,
NativeKeyCode::Android(x) => NativeKey::Android(x),
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
}
}
}
impl PartialEq<NativeKey> for NativeKeyCode {
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
NativeKey::from(*self) == *rhs
}
}
impl PartialEq<NativeKeyCode> for NativeKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
rhs == self
}
}
/// Represents the location of a physical key.
///
/// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified)
/// variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhysicalKey {
/// A known key code
Code(KeyCode),
/// This variant is used when the key cannot be translated to a [`KeyCode`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
}
impl From<KeyCode> for PhysicalKey {
#[inline]
fn from(code: KeyCode) -> Self {
PhysicalKey::Code(code)
}
}
impl From<NativeKeyCode> for PhysicalKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
PhysicalKey::Unidentified(code)
}
}
impl PartialEq<KeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &KeyCode) -> bool {
match self {
PhysicalKey::Code(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for KeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
impl PartialEq<NativeKeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
match self {
PhysicalKey::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for NativeKeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
/// Code representing the location of a physical key
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
/// exceptions:
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
/// "SuperRight" here.
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyCode {
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`KeyCode`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
/// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
/// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
/// (hankaku/zenkaku/kanji) key on Japanese keyboards
@@ -648,7 +734,7 @@ pub enum KeyCode {
F35,
}
/// Key represents the meaning of a keypress.
/// A [`Key::Named`] value
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few
/// exceptions:
@@ -656,32 +742,12 @@ pub enum KeyCode {
/// another key which the specification calls `Super`. That does not exist here.)
/// - The `Space` variant here, can be identified by the character it generates in the
/// specificaiton.
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
pub enum NamedKey {
/// The `Alt` (Alternative) key.
///
/// This key enables the alternate modifier function for interpreting concurrent or subsequent
@@ -1385,83 +1451,131 @@ pub enum Key<Str = SmolStr> {
F35,
}
macro_rules! map_match {
(
$to_match:expr,
// Custom match arms
{ $( $from:pat => $to:expr ),* },
// The enum's name
$prefix:path,
// Trivial match arms for unit variants
{ $( $t:tt ),* }) => {
match $to_match {
$( $from => $to, )*
$( Key::$t => Key::$t, )*
/// Key represents the meaning of a keypress.
///
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions:
/// - All simple variants are wrapped under the `Named` variant
/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A simple (unparameterised) action
Named(NamedKey),
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
}
impl From<NamedKey> for Key {
#[inline]
fn from(action: NamedKey) -> Self {
Key::Named(action)
}
}
impl From<NativeKey> for Key {
#[inline]
fn from(code: NativeKey) -> Self {
Key::Unidentified(code)
}
}
impl<Str> PartialEq<NamedKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NamedKey) -> bool {
match self {
Key::Named(ref a) => a == rhs,
_ => false,
}
};
}
}
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
match self {
Key::Character(ref s) => s == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}
impl<Str> PartialEq<NativeKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
match self {
Key::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl<Str> PartialEq<Key<Str>> for NativeKey {
#[inline]
fn eq(&self, rhs: &Key<Str>) -> bool {
rhs == self
}
}
impl Key<SmolStr> {
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
/// `Key`. All other variants remain unchanged.
pub fn as_ref(&self) -> Key<&str> {
map_match!(
self,
{
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone())
},
Key,
{
Alt, AltGraph, CapsLock, Control, Fn, FnLock, NumLock, ScrollLock, Shift, Symbol,
SymbolLock, Meta, Hyper, Super, Enter, Tab, Space, ArrowDown, ArrowLeft,
ArrowRight, ArrowUp, End, Home, PageDown, PageUp, Backspace, Clear, Copy, CrSel,
Cut, Delete, EraseEof, ExSel, Insert, Paste, Redo, Undo, Accept, Again, Attn,
Cancel, ContextMenu, Escape, Execute, Find, Help, Pause, Play, Props, Select,
ZoomIn, ZoomOut, BrightnessDown, BrightnessUp, Eject, LogOff, Power, PowerOff,
PrintScreen, Hibernate, Standby, WakeUp, AllCandidates, Alphanumeric, CodeInput,
Compose, Convert, FinalMode, GroupFirst, GroupLast, GroupNext, GroupPrevious,
ModeChange, NextCandidate, NonConvert, PreviousCandidate, Process, SingleCandidate,
HangulMode, HanjaMode, JunjaMode, Eisu, Hankaku, Hiragana, HiraganaKatakana,
KanaMode, KanjiMode, Katakana, Romaji, Zenkaku, ZenkakuHankaku, Soft1, Soft2,
Soft3, Soft4, ChannelDown, ChannelUp, Close, MailForward, MailReply, MailSend,
MediaClose, MediaFastForward, MediaPause, MediaPlay, MediaPlayPause, MediaRecord,
MediaRewind, MediaStop, MediaTrackNext, MediaTrackPrevious, New, Open, Print, Save,
SpellCheck, Key11, Key12, AudioBalanceLeft, AudioBalanceRight, AudioBassBoostDown,
AudioBassBoostToggle, AudioBassBoostUp, AudioFaderFront, AudioFaderRear,
AudioSurroundModeNext, AudioTrebleDown, AudioTrebleUp, AudioVolumeDown,
AudioVolumeUp, AudioVolumeMute, MicrophoneToggle, MicrophoneVolumeDown,
MicrophoneVolumeUp, MicrophoneVolumeMute, SpeechCorrectionList, SpeechInputToggle,
LaunchApplication1, LaunchApplication2, LaunchCalendar, LaunchContacts, LaunchMail,
LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver,
LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor,
BrowserBack, BrowserFavorites, BrowserForward, BrowserHome, BrowserRefresh,
BrowserSearch, BrowserStop, AppSwitch, Call, Camera, CameraFocus, EndCall, GoBack,
GoHome, HeadsetHook, LastNumberRedial, Notification, MannerMode, VoiceDial, TV,
TV3DMode, TVAntennaCable, TVAudioDescription, TVAudioDescriptionMixDown,
TVAudioDescriptionMixUp, TVContentsMenu, TVDataService, TVInput, TVInputComponent1,
TVInputComponent2, TVInputComposite1, TVInputComposite2, TVInputHDMI1,
TVInputHDMI2, TVInputHDMI3, TVInputHDMI4, TVInputVGA1, TVMediaContext, TVNetwork,
TVNumberEntry, TVPower, TVRadioService, TVSatellite, TVSatelliteBS, TVSatelliteCS,
TVSatelliteToggle, TVTerrestrialAnalog, TVTerrestrialDigital, TVTimer, AVRInput,
AVRPower, ColorF0Red, ColorF1Green, ColorF2Yellow, ColorF3Blue, ColorF4Grey,
ColorF5Brown, ClosedCaptionToggle, Dimmer, DisplaySwap, DVR, Exit, FavoriteClear0,
FavoriteClear1, FavoriteClear2, FavoriteClear3, FavoriteRecall0, FavoriteRecall1,
FavoriteRecall2, FavoriteRecall3, FavoriteStore0, FavoriteStore1, FavoriteStore2,
FavoriteStore3, Guide, GuideNextDay, GuidePreviousDay, Info, InstantReplay, Link,
ListProgram, LiveContent, Lock, MediaApps, MediaAudioTrack, MediaLast,
MediaSkipBackward, MediaSkipForward, MediaStepBackward, MediaStepForward,
MediaTopMenu, NavigateIn, NavigateNext, NavigateOut, NavigatePrevious,
NextFavoriteChannel, NextUserProfile, OnDemand, Pairing, PinPDown, PinPMove,
PinPToggle, PinPUp, PlaySpeedDown, PlaySpeedReset, PlaySpeedUp, RandomToggle,
RcLowBattery, RecordSpeedNext, RfBypass, ScanChannelsToggle, ScreenModeNext,
Settings, SplitScreenToggle, STBInput, STBPower, Subtitle, Teletext, VideoModeNext,
Wink, ZoomToggle, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15,
F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31,
F32, F33, F34, F35
}
)
match self {
Key::Named(a) => Key::Named(*a),
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone()),
}
}
}
impl NamedKey {
/// Convert an action to its approximate textual equivalent.
///
/// # Examples
///
/// ```
/// use winit::keyboard::NamedKey;
///
/// assert_eq!(NamedKey::Enter.to_text(), Some("\r"));
/// assert_eq!(NamedKey::F20.to_text(), None);
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
NamedKey::Enter => Some("\r"),
NamedKey::Backspace => Some("\x08"),
NamedKey::Tab => Some("\t"),
NamedKey::Space => Some(" "),
NamedKey::Escape => Some("\x1b"),
_ => None,
}
}
}
@@ -1471,20 +1585,16 @@ impl Key {
/// # Examples
///
/// ```
/// use winit::keyboard::Key;
/// use winit::keyboard::{NamedKey, Key};
///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Enter.to_text(), Some("\r"));
/// assert_eq!(Key::F20.to_text(), None);
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
Key::Named(action) => action.to_text(),
Key::Character(ch) => Some(ch.as_str()),
Key::Enter => Some("\r"),
Key::Backspace => Some("\x08"),
Key::Tab => Some("\t"),
Key::Space => Some(" "),
Key::Escape => Some("\x1b"),
_ => None,
}
}
@@ -1572,7 +1682,7 @@ bitflags! {
/// Represents the current state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModifiersState: u32 {
/// The "shift" key.
const SHIFT = 0b100;

View File

@@ -7,7 +7,7 @@
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new();
//! let event_loop = EventLoop::new().unwrap();
//! ```
//!
//! Once this is done there are two ways to create a [`Window`]:
@@ -30,46 +30,44 @@
//!
//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until the `control_flow` argument given to the closure is set to
//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which
//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates.
//! will run until [`exit()`] is used, at which point [`Event`]`::`[`LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why
//! [`EventLoopExtPumpEvents::pump_events`]. See that method's documentation for more reasons about why
//! it's discouraged, beyond compatibility reasons.
//!
//!
//! ```no_run
//! use winit::{
//! event::{Event, WindowEvent},
//! event_loop::EventLoop,
//! event_loop::{ControlFlow, EventLoop},
//! window::WindowBuilder,
//! };
//!
//! let event_loop = EventLoop::new();
//! let event_loop = EventLoop::new().unwrap();
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
//!
//! event_loop.run(move |event, _, control_flow| {
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! control_flow.set_poll();
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! event_loop.set_control_flow(ControlFlow::Poll);
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! control_flow.set_wait();
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! event_loop.run(move |event, elwt| {
//! match event {
//! Event::WindowEvent {
//! event: WindowEvent::CloseRequested,
//! ..
//! } => {
//! println!("The close button was pressed; stopping");
//! control_flow.set_exit();
//! elwt.exit();
//! },
//! Event::MainEventsCleared => {
//! Event::AboutToWait => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
@@ -79,11 +77,14 @@
//! // can just render here instead.
//! window.request_redraw();
//! },
//! Event::RedrawRequested(_) => {
//! Event::WindowEvent {
//! event: WindowEvent::RedrawRequested,
//! ..
//! } => {
//! // Redraw the application.
//! //
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // this event rather than in AboutToWait, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
@@ -109,12 +110,10 @@
//! window visible only once you're ready to render into it.
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return
//! [`EventLoopExtPumpEvents::pump_events`]: ./platform/pump_events/trait.EventLoopExtPumpEvents.html#tymethod.pump_events
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [event_loop_run]: event_loop::EventLoop::run
//! [`ControlFlow`]: event_loop::ControlFlow
//! [`Exit`]: event_loop::ControlFlow::Exit
//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode
//! [`exit()`]: event_loop::EventLoopWindowTarget::exit
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowBuilder`]: window::WindowBuilder
@@ -126,7 +125,7 @@
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`UserEvent`]: event::Event::UserEvent
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
//! [`LoopExiting`]: event::Event::LoopExiting
//! [`platform`]: platform
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
@@ -134,11 +133,15 @@
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
@@ -160,3 +163,18 @@ mod platform_impl;
pub mod window;
pub mod platform;
/// Wrapper for objects which winit will access on the main thread so they are effectively `Send`
/// and `Sync`, since they always excute on a single thread.
///
/// # Safety
///
/// Winit can run only one event loop at the time and the event loop itself is tied to some thread.
/// The objects could be send across the threads, but once passed to winit, they execute on the
/// mean thread if the platform demands it. Thus marking such objects as `Send + Sync` is safe.
#[doc(hidden)]
#[derive(Clone, Debug)]
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);
unsafe impl<T> Send for SendSyncWrapper<T> {}
unsafe impl<T> Sync for SendSyncWrapper<T> {}

View File

@@ -108,20 +108,12 @@ impl MonitorHandle {
/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// - **Web:** Always returns None
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the monitor's resolution.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.inner.size()
@@ -129,10 +121,6 @@ impl MonitorHandle {
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
///
/// ## Platform-specific
///
/// - **Web:** Always returns (0,0)
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position()
@@ -158,7 +146,6 @@ impl MonitorHandle {
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns 1.0
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()

View File

@@ -1,5 +1,6 @@
use std::os::raw::c_void;
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use crate::{
@@ -22,27 +23,6 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Returns a pointer to the [`UIWindow`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the [`UIViewController`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the [`UIView`] that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void;
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
@@ -89,53 +69,63 @@ pub trait WindowExtIOS {
///
/// The default is to prefer showing the status bar.
///
/// This changes the value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc),
/// and then calls
/// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc).
/// This sets the value of the
/// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_prefers_status_bar_hidden(&self, hidden: bool);
/// Sets the preferred status bar style for the [`Window`].
///
/// The default is system-defined.
///
/// This sets the value of the
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
/// property.
///
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
/// is also called for you.
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
}
impl WindowExtIOS for Window {
#[inline]
fn ui_window(&self) -> *mut c_void {
self.window.ui_window()
}
#[inline]
fn ui_view_controller(&self) -> *mut c_void {
self.window.ui_view_controller()
}
#[inline]
fn ui_view(&self) -> *mut c_void {
self.window.ui_view()
}
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window.set_scale_factor(scale_factor)
self.window
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations)
self.window
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden)
self.window
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
}
#[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window
.set_preferred_screen_edges_deferring_system_gestures(edges)
self.window.maybe_queue_on_main(move |w| {
w.set_preferred_screen_edges_deferring_system_gestures(edges)
})
}
#[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window.set_prefers_status_bar_hidden(hidden)
self.window
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
}
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
}
@@ -148,7 +138,7 @@ pub trait WindowBuilderExtIOS {
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder;
fn with_scale_factor(self, scale_factor: f64) -> Self;
/// Sets the valid orientations for the [`Window`].
///
@@ -156,7 +146,7 @@ pub trait WindowBuilderExtIOS {
///
/// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
@@ -166,7 +156,7 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
@@ -175,10 +165,7 @@ pub trait WindowBuilderExtIOS {
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
///
/// This only has an effect on iOS 11.0+.
fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
/// Sets whether the [`Window`] prefers the status bar hidden.
///
@@ -186,43 +173,54 @@ pub trait WindowBuilderExtIOS {
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder;
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
/// Sets the style of the [`Window`]'s status bar.
///
/// The default is system-defined.
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
}
impl WindowBuilderExtIOS for WindowBuilder {
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder {
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor);
self
}
#[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder {
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.platform_specific.valid_orientations = valid_orientations;
self
}
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
}
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> WindowBuilder {
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self
}
#[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder {
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
#[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.platform_specific.preferred_status_bar_style = status_bar_style;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
@@ -241,7 +239,9 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
@@ -298,3 +298,11 @@ bitflags! {
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum StatusBarStyle {
#[default]
Default,
LightContent,
DarkContent,
}

View File

@@ -10,16 +10,6 @@ use crate::{
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_window(&self) -> *mut c_void;
/// Returns a pointer to the cocoa `NSView` that is used by this window.
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn ns_view(&self) -> *mut c_void;
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
@@ -38,6 +28,28 @@ pub trait WindowExtMacOS {
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
/// Group windows together by using the same tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn set_tabbing_identifier(&self, identifier: &str);
/// Returns the window's tabbing identifier.
fn tabbing_identifier(&self) -> String;
/// Select next tab.
fn select_next_tab(&self);
/// Select previous tab.
fn select_previous_tab(&self);
/// Select the tab with the given index.
///
/// Will no-op when the index is out of bounds.
fn select_tab_at_index(&self, index: usize);
/// Get the number of tabs in the window tab group.
fn num_tabs(&self) -> usize;
/// Get the window's edit state.
///
/// # Examples
@@ -70,54 +82,80 @@ pub trait WindowExtMacOS {
}
impl WindowExtMacOS for Window {
#[inline]
fn ns_window(&self) -> *mut c_void {
self.window.ns_window()
}
#[inline]
fn ns_view(&self) -> *mut c_void {
self.window.ns_view()
}
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.simple_fullscreen()
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.set_simple_fullscreen(fullscreen)
self.window
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
fn has_shadow(&self) -> bool {
self.window.has_shadow()
self.window.maybe_wait_on_main(|w| w.has_shadow())
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window.set_has_shadow(has_shadow)
self.window
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
self.window
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
fn tabbing_identifier(&self) -> String {
self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
}
#[inline]
fn select_next_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_next_tab())
}
#[inline]
fn select_previous_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
self.window
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
}
#[inline]
fn num_tabs(&self) -> usize {
self.window.maybe_wait_on_main(|w| w.num_tabs())
}
#[inline]
fn is_document_edited(&self) -> bool {
self.window.is_document_edited()
self.window.maybe_wait_on_main(|w| w.is_document_edited())
}
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window.set_document_edited(edited)
self.window
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.set_option_as_alt(option_as_alt)
self.window
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.option_as_alt()
self.window.maybe_wait_on_main(|w| w.option_as_alt())
}
}
@@ -145,88 +183,96 @@ pub enum ActivationPolicy {
/// - `with_fullsize_content_view`
pub trait WindowBuilderExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool)
-> WindowBuilder;
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
/// Makes the titlebar transparent and allows the content to appear behind it.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
/// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
fn with_title_hidden(self, title_hidden: bool) -> Self;
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
/// Makes the window content appear behind the titlebar.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
fn with_has_shadow(self, has_shadow: bool) -> Self;
/// Window accepts click-through mouse events.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
/// Defines the window tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn with_tabbing_identifier(self, identifier: &str) -> Self;
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
}
impl WindowBuilderExtMacOS for WindowBuilder {
#[inline]
fn with_movable_by_window_background(
mut self,
movable_by_window_background: bool,
) -> WindowBuilder {
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
}
#[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> WindowBuilder {
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific
.tabbing_identifier
.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.platform_specific.option_as_alt = option_as_alt;
self
}
@@ -329,6 +375,12 @@ pub trait EventLoopWindowTargetExtMacOS {
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
/// Returns whether the system can automatically organize windows into tabs.
fn allows_automatic_window_tabbing(&self) -> bool;
}
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
@@ -339,6 +391,14 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
fn hide_other_applications(&self) {
self.p.hide_other_applications()
}
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
self.p.set_allows_automatic_window_tabbing(enabled);
}
fn allows_automatic_window_tabbing(&self) -> bool {
self.p.allows_automatic_window_tabbing()
}
}
/// Option as alt behavior.

View File

@@ -9,9 +9,10 @@
//! - `windows`
//! - `web`
//!
//! And the following platform-specific module:
//! And the following platform-specific modules:
//!
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
//! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`)
//! - `pump_events` (available on `windows`, `unix`, `macos`, `android`)
//!
//! However only the module corresponding to the platform you're compiling to will be available.
@@ -23,6 +24,8 @@ pub mod ios;
pub mod macos;
#[cfg(orbital_platform)]
pub mod orbital;
#[cfg(any(x11_platform, wayland_platform))]
pub mod startup_notify;
#[cfg(wayland_platform)]
pub mod wayland;
#[cfg(wasm_platform)]
@@ -32,14 +35,23 @@ pub mod windows;
#[cfg(x11_platform)]
pub mod x11;
pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
orbital_platform
wayland_platform
))]
pub mod run_return;
pub mod run_on_demand;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
))]
pub mod pump_events;
pub mod modifier_supplement;
pub mod scancode;

189
src/platform/pump_events.rs Normal file
View File

@@ -0,0 +1,189 @@
use std::time::Duration;
use crate::{
event::Event,
event_loop::{EventLoop, EventLoopWindowTarget},
};
/// The return status for `pump_events`
pub enum PumpStatus {
/// Continue running external loop.
Continue,
/// Exit external loop.
Exit(i32),
}
/// 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`].
type UserEvent;
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
/// external event loop, for platforms that can support this.
///
/// The given `timeout` limits how long it may block waiting for new events.
///
/// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external
/// event loop is never blocked but you would likely need to consider how
/// to throttle your own external loop.
///
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop.
///
/// ## Example
///
/// ```rust,no_run
/// # // Copied from examples/window_pump_events.rs
/// # #[cfg(any(
/// # windows_platform,
/// # macos_platform,
/// # x11_platform,
/// # wayland_platform,
/// # android_platform,
/// # ))]
/// fn main() -> std::process::ExitCode {
/// # use std::{process::ExitCode, thread::sleep, time::Duration};
/// #
/// # use simple_logger::SimpleLogger;
/// # use winit::{
/// # event::{Event, WindowEvent},
/// # event_loop::EventLoop,
/// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
/// # window::WindowBuilder,
/// # };
/// let mut event_loop = EventLoop::new().unwrap();
/// #
/// # SimpleLogger::new().init().unwrap();
/// let window = WindowBuilder::new()
/// .with_title("A fantastic window!")
/// .build(&event_loop)
/// .unwrap();
///
/// 'main: loop {
/// let timeout = Some(Duration::ZERO);
/// let status = event_loop.pump_events(timeout, |event, elwt| {
/// # if let Event::WindowEvent { event, .. } = &event {
/// # // Print only Window events to reduce noise
/// # println!("{event:?}");
/// # }
/// #
/// match event {
/// Event::WindowEvent {
/// event: WindowEvent::CloseRequested,
/// window_id,
/// } if window_id == window.id() => elwt.exit(),
/// Event::AboutToWait => {
/// window.request_redraw();
/// }
/// _ => (),
/// }
/// });
/// if let PumpStatus::Exit(exit_code) = status {
/// break 'main ExitCode::from(exit_code as u8);
/// }
///
/// // Sleep for 1/60 second to simulate application work
/// //
/// // Since `pump_events` doesn't block it will be important to
/// // throttle the loop in the app somehow.
/// println!("Update()");
/// sleep(Duration::from_millis(16));
/// }
/// }
/// ```
///
/// **Note:** This is not a portable API, and its usage involves a number of
/// caveats and trade offs that should be considered before using this API!
///
/// You almost certainly shouldn't use this API, unless you absolutely know it's
/// the only practical option you have.
///
/// ## Synchronous events
///
/// Some events _must_ only be handled synchronously via the closure that
/// is passed to Winit so that the handler will also be synchronized with
/// the window system and operating system.
///
/// This is because some events are driven by a window system callback
/// where the window systems expects the application to have handled the
/// event before returning.
///
/// **These events can not be buffered and handled outside of the closure
/// passed to Winit.**
///
/// As a general rule it is not recommended to ever buffer events to handle
/// them outside of the closure passed to Winit since it's difficult to
/// provide guarantees about which events are safe to buffer across all
/// operating systems.
///
/// Notable events that will certainly create portability problems if
/// buffered and handled outside of Winit include:
/// - `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.
///
/// For portability it's strongly recommended that applications should
/// 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.
///
/// ## Supported Platforms
/// - Windows
/// - Linux
/// - MacOS
/// - Android
///
/// ## 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.
/// - **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.
///
/// ## Platform-specific
/// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global `NSApp`
/// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApp` which aren't accessible to application
/// developers)
///
/// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApp` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApp` is global state.
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.pump_events(timeout, event_handler)
}
}

View File

@@ -0,0 +1,76 @@
use crate::{
error::EventLoopError,
event::Event,
event_loop::{EventLoop, EventLoopWindowTarget},
};
#[cfg(doc)]
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`].
type UserEvent;
/// Runs the event loop in the calling thread and calls the given `event_handler` closure
/// to dispatch any window system events.
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
///
/// It's expected that each run of the loop will be for orthogonal instantiations of your
/// Winit application, but internally each instantiation may re-use some common window
/// system resources, such as a display server connection.
///
/// This API is not designed to run an event loop in bursts that you can exit from and return
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API)
///
/// Each time `run_on_demand` is called the `event_handler` can expect to receive a
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume
/// lifecycle) - which can be used to consistently initialize application state.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # 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 `spawn()` 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()`] for portability, unless you specifically need
/// the ability to re-run a single event loop more than once
///
/// # Supported Platforms
/// - Windows
/// - Linux
/// - macOS
/// - Android
///
/// # 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 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.
///
/// [`exit()`]: EventLoopWindowTarget::exit
/// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.run_on_demand(event_handler)
}
}

View File

@@ -1,53 +0,0 @@
use crate::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunReturn {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent;
/// Initializes the `winit` event loop.
///
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
///
/// # Caveats
///
/// Despite its appearance at first glance, this is *not* a perfect replacement for
/// `poll_events`. For example, this function will not return on Windows or macOS while a
/// window is getting resized, resulting in all application logic outside of the
/// `event_handler` closure not running until the resize operation ends. Other OS operations
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
///
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
///
/// ## Platform-specific
///
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
/// the display server.
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
}
impl<T> EventLoopExtRunReturn for EventLoop<T> {
type UserEvent = T;
fn run_return<F>(&mut self, event_handler: F) -> i32
where
F: FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
{
self.event_loop.run_return(event_handler)
}
}

View File

@@ -1,14 +1,14 @@
#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))]
use crate::keyboard::KeyCode;
use crate::keyboard::{KeyCode, PhysicalKey};
// TODO: Describe what this value contains for each platform
/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific
/// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific
/// scancode.
///
/// [`KeyCode`]: crate::keyboard::KeyCode
pub trait KeyCodeExtScancode {
/// [`PhysicalKey`]: crate::keyboard::PhysicalKey
pub trait PhysicalKeyExtScancode {
/// The raw value of the platform-specific physical key identifier.
///
/// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise.
@@ -18,13 +18,28 @@ pub trait KeyCodeExtScancode {
/// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8.
fn to_scancode(self) -> Option<u32>;
/// Constructs a `KeyCode` from a platform-specific physical key identifier.
/// Constructs a `PhysicalKey` from a platform-specific physical key identifier.
///
/// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back
/// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back
/// using `to_scancode` might not yield the original value.
///
/// ## Platform-specific
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
/// `8` to get the value you wanted.
fn from_scancode(scancode: u32) -> KeyCode;
fn from_scancode(scancode: u32) -> PhysicalKey;
}
impl PhysicalKeyExtScancode for KeyCode
where
PhysicalKey: PhysicalKeyExtScancode,
{
#[inline]
fn from_scancode(scancode: u32) -> PhysicalKey {
<PhysicalKey as PhysicalKeyExtScancode>::from_scancode(scancode)
}
#[inline]
fn to_scancode(self) -> Option<u32> {
<PhysicalKey as PhysicalKeyExtScancode>::to_scancode(PhysicalKey::Code(self))
}
}

View File

@@ -0,0 +1,99 @@
//! Window startup notification to handle window raising.
//!
//! The [`ActivationToken`] is essential to ensure that your newly
//! created window will obtain the focus, otherwise the user could
//! be requered to click on the window.
//!
//! Such token is usually delivered via the environment variable and
//! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`].
//!
//! Such token must also be reset after reading it from your environment with
//! [`reset_activation_token_env`] otherwise child processes could inherit it.
//!
//! When starting a new child process with a newly obtained [`ActivationToken`] from
//! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`]
//! must be used to propagate it to the child
//!
//! To ensure the delivery of such token by other processes to you, the user should
//! set `StartupNotify=true` inside the `.desktop` file of their application.
//!
//! The specification could be found [`here`].
//!
//! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt
use std::env;
use crate::error::NotSupportedError;
use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget};
use crate::window::{ActivationToken, Window, WindowBuilder};
/// The variable which is used mostly on X11.
const X11_VAR: &str = "DESKTOP_STARTUP_ID";
/// The variable which is used mostly on Wayland.
const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN";
pub trait EventLoopExtStartupNotify {
/// Read the token from the environment.
///
/// It's recommended **to unset** this environment variable for child processes.
fn read_token_from_env(&self) -> Option<ActivationToken>;
}
pub trait WindowExtStartupNotify {
/// Request a new activation token.
///
/// The token will be delivered inside
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
}
pub trait WindowBuilderExtStartupNotify {
/// Use this [`ActivationToken`] during window creation.
///
/// Not using such a token upon a window could make your window not gaining
/// focus until the user clicks on the window.
fn with_activation_token(self, token: ActivationToken) -> Self;
}
impl<T> EventLoopExtStartupNotify for EventLoopWindowTarget<T> {
fn read_token_from_env(&self) -> Option<ActivationToken> {
match self.p {
#[cfg(wayland_platform)]
crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR),
#[cfg(x11_platform)]
crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR),
}
.ok()
.map(ActivationToken::_new)
}
}
impl WindowExtStartupNotify for Window {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
self.window.request_activation_token()
}
}
impl WindowBuilderExtStartupNotify for WindowBuilder {
fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.platform_specific.activation_token = Some(token);
self
}
}
/// Remove the activation environment variables from the current process.
///
/// This is wise to do before running child processes,
/// which may not to support the activation token.
pub fn reset_activation_token_env() {
env::remove_var(X11_VAR);
env::remove_var(WAYLAND_VAR);
}
/// Set environment variables responsible for activation token.
///
/// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
}

View File

@@ -1,17 +1,10 @@
use std::os::raw;
use sctk::reexports::client::Proxy;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
window::{Window, WindowBuilder},
};
use crate::platform_impl::{
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
Window as LinuxWindow,
};
use crate::platform_impl::{ApplicationName, Backend};
pub use crate::window::Theme;
@@ -19,16 +12,6 @@ pub use crate::window::Theme;
pub trait EventLoopWindowTargetExtWayland {
/// True if the [`EventLoopWindowTarget`] uses Wayland.
fn is_wayland(&self) -> bool;
/// Returns a pointer to the `wl_display` object of wayland that is used by this
/// [`EventLoopWindowTarget`].
///
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
///
/// [`EventLoop`]: crate::event_loop::EventLoop
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
@@ -36,17 +19,6 @@ impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
fn is_wayland(&self) -> bool {
self.p.is_wayland()
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.connection.display().id().as_ptr() as *mut _)
}
#[cfg(x11_platform)]
_ => None,
}
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
@@ -76,41 +48,9 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
}
/// Additional methods on [`Window`] that are specific to Wayland.
pub trait WindowExtWayland {
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
pub trait WindowExtWayland {}
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
///
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn wayland_display(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
}
}
impl WindowExtWayland for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to Wayland.
pub trait WindowBuilderExtWayland {

View File

@@ -2,24 +2,58 @@
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
//! to provide your own canvas.
//!
//! It is recommended **not** to apply certain CSS properties to the canvas:
//! - [`transform`]
//! - [`border`]
//! - [`padding`]
//!
//! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`],
//! and [`WindowEvent::Touch`].
//! - [`Window::set_outer_position()`]
//!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size()
//! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded
//! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved
//! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered
//! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft
//! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position()
//! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform
//! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border
//! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
use crate::event::Event;
use crate::event_loop::ControlFlow;
use crate::event_loop::EventLoop;
use crate::event_loop::EventLoopWindowTarget;
use crate::window::WindowBuilder;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;
use web_sys::HtmlCanvasElement;
pub trait WindowExtWebSys {
/// Only returns the canvas if called from inside the window.
fn canvas(&self) -> Option<HtmlCanvasElement>;
}
/// Whether the browser reports the preferred color scheme to be "dark".
fn is_dark_mode(&self) -> bool;
impl WindowExtWebSys for Window {
#[inline]
fn canvas(&self) -> Option<HtmlCanvasElement> {
self.window.canvas()
}
}
pub trait WindowBuilderExtWebSys {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// [`WindowBuilder::build()`] will create one.
///
/// In any case, the canvas won't be automatically inserted into the web page.
///
/// [`None`] by default.
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
@@ -30,29 +64,40 @@ pub trait WindowBuilderExtWebSys {
///
/// Some events are impossible to prevent. E.g. Firefox allows to access the native browser
/// context menu with Shift+Rightclick.
///
/// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
/// canvas keyboard events.
///
/// Enabled by default.
fn with_focusable(self, focusable: bool) -> Self;
/// On window creation, append the canvas element to the web page if it isn't already.
///
/// Disabled by default.
fn with_append(self, append: bool) -> Self;
}
impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = canvas;
self.platform_specific.canvas = SendSyncWrapper(canvas);
self
}
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
}
fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable;
self
}
fn with_append(mut self, append: bool) -> Self {
self.platform_specific.append = append;
self
}
}
@@ -73,12 +118,7 @@ pub trait EventLoopExtWebSys {
/// event loop when switching between tabs on a single page application.
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
);
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>);
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -86,13 +126,62 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
fn spawn<F>(self, event_handler: F)
where
F: 'static
+ FnMut(
Event<'_, Self::UserEvent>,
&EventLoopWindowTarget<Self::UserEvent>,
&mut ControlFlow,
),
F: 'static + FnMut(Event<Self::UserEvent>, &EventLoopWindowTarget<Self::UserEvent>),
{
self.event_loop.spawn(event_handler)
}
}
pub trait EventLoopWindowTargetExtWebSys {
/// 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;
}
impl<T> EventLoopWindowTargetExtWebSys for EventLoopWindowTarget<T> {
#[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.p.set_poll_strategy(strategy);
}
#[inline]
fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy()
}
}
/// Strategy used for [`ControlFlow::Poll`](crate::event_loop::ControlFlow::Poll).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PollStrategy {
/// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy will wait for the browser to enter an idle period before running and might
/// be affected by browser throttling.
///
/// [`Window.requestIdleCallback()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
IdleCallback,
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy will run as fast as possible without disturbing users from interacting with
/// the page and is not affected by browser throttling.
///
/// 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,
}

View File

@@ -17,8 +17,6 @@ pub type HWND = isize;
pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
/// Instance Handle type used by Win32 API
pub type HINSTANCE = isize;
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows {
@@ -113,13 +111,6 @@ impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Returns the HINSTANCE of the window
fn hinstance(&self) -> HINSTANCE;
/// Returns the native handle that is used by this window.
///
/// The pointer will become invalid when the native window was destroyed.
fn hwnd(&self) -> HWND;
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
@@ -148,16 +139,6 @@ pub trait WindowExtWindows {
}
impl WindowExtWindows for Window {
#[inline]
fn hinstance(&self) -> HINSTANCE {
self.window.hinstance()
}
#[inline]
fn hwnd(&self) -> HWND {
self.window.hwnd()
}
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
@@ -180,6 +161,7 @@ impl WindowExtWindows for Window {
}
/// Additional methods on `WindowBuilder` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowBuilderExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`.
@@ -192,7 +174,7 @@ pub trait WindowBuilderExtWindows {
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
fn with_owner_window(self, parent: HWND) -> WindowBuilder;
fn with_owner_window(self, parent: HWND) -> Self;
/// Sets a menu on the window to be created.
///
@@ -204,13 +186,13 @@ pub trait WindowBuilderExtWindows {
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu
fn with_menu(self, menu: HMENU) -> WindowBuilder;
fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
@@ -218,57 +200,66 @@ pub trait WindowBuilderExtWindows {
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> WindowBuilder;
fn with_drag_and_drop(self, flag: bool) -> Self;
/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
fn with_skip_taskbar(self, skip: bool) -> Self;
/// Customize the window class name.
fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
fn with_undecorated_shadow(self, shadow: bool) -> Self;
}
impl WindowBuilderExtWindows for WindowBuilder {
#[inline]
fn with_owner_window(mut self, parent: HWND) -> WindowBuilder {
fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent);
self
}
#[inline]
fn with_menu(mut self, menu: HMENU) -> WindowBuilder {
fn with_menu(mut self, menu: HMENU) -> Self {
self.platform_specific.menu = Some(menu);
self
}
#[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}
#[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
self.platform_specific.no_redirection_bitmap = flag;
self
}
#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder {
fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.platform_specific.drag_and_drop = flag;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
fn with_skip_taskbar(mut self, skip: bool) -> Self {
self.platform_specific.skip_taskbar = skip;
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
self.platform_specific.class_name = class_name.into();
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.platform_specific.decoration_shadow = shadow;
self
}

View File

@@ -1,6 +1,3 @@
use std::os::raw;
use std::ptr;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
@@ -8,9 +5,7 @@ use crate::{
};
use crate::dpi::Size;
use crate::platform_impl::{
x11::ffi::XVisualInfo, ApplicationName, Backend, Window as LinuxWindow, XLIB_ERROR_HOOKS,
};
use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS};
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
@@ -22,6 +17,12 @@ pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupport
pub type XlibErrorHook =
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
/// A unique identifer for an X11 visual.
pub type XVisualID = u32;
/// A unique identifier for an X11 window.
pub type XWindow = u32;
/// Hook to winit's xlib error handling callback.
///
/// This method is provided as a safe way to handle the errors comming from X11
@@ -81,70 +82,14 @@ impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
}
/// Additional methods on [`Window`] that are specific to X11.
pub trait WindowExtX11 {
/// Returns the ID of the [`Window`] xlib object that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
fn xlib_window(&self) -> Option<raw::c_ulong>;
pub trait WindowExtX11 {}
/// Returns a pointer to the `Display` object of xlib that is used by this window.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xlib_display(&self) -> Option<*mut raw::c_void>;
fn xlib_screen_id(&self) -> Option<raw::c_int>;
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
///
/// The pointer will become invalid when the [`Window`] is destroyed.
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
}
impl WindowExtX11 for Window {
#[inline]
fn xlib_window(&self) -> Option<raw::c_ulong> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_window()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_display()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xlib_screen_id(&self) -> Option<raw::c_int> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
#[cfg(wayland_platform)]
_ => None,
}
}
#[inline]
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
#[cfg(wayland_platform)]
_ => None,
}
}
}
impl WindowExtX11 for Window {}
/// Additional methods on [`WindowBuilder`] that are specific to X11.
pub trait WindowBuilderExtX11 {
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
/// Create this window with a specific X11 visual.
fn with_x11_visual(self, visual_id: XVisualID) -> Self;
fn with_x11_screen(self, screen_id: i32) -> Self;
@@ -176,21 +121,35 @@ pub trait WindowBuilderExtX11 {
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
/// Embed this window into another parent window.
///
/// # Example
///
/// ```no_run
/// use winit::window::WindowBuilder;
/// use winit::platform::x11::{XWindow, WindowBuilderExtX11};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = winit::event_loop::EventLoop::new().unwrap();
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window = WindowBuilder::new()
/// .with_embed_parent_window(parent_window_id)
/// .build(&event_loop)?;
/// # Ok(()) }
/// ```
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
}
impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
{
self.platform_specific.visual_infos =
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
}
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.platform_specific.x11.visual_id = Some(visual_id);
self
}
#[inline]
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.screen_id = Some(screen_id);
self.platform_specific.x11.screen_id = Some(screen_id);
self
}
@@ -202,19 +161,25 @@ impl WindowBuilderExtX11 for WindowBuilder {
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.override_redirect = override_redirect;
self.platform_specific.x11.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
self.platform_specific.x11_window_types = x11_window_types;
self.platform_specific.x11.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());
self.platform_specific.x11.base_size = Some(base_size.into());
self
}
#[inline]
fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
self.platform_specific.x11.embed_window = Some(parent_window_id);
self
}
}

View File

@@ -1,9 +1,12 @@
use android_activity::input::Keycode;
use android_activity::{
input::{KeyAction, KeyEvent, KeyMapChar, Keycode},
AndroidApp,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
match keycode {
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode {
Keycode::A => KeyCode::KeyA,
Keycode::B => KeyCode::KeyB,
Keycode::C => KeyCode::KeyC,
@@ -152,328 +155,411 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp,
keycode => KeyCode::Unidentified(NativeKeyCode::Android(keycode.into())),
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
})
}
/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent
///
/// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and
/// uses that to try and map the `key_code` + `meta_state` to a unicode
/// character or a dead key that can be combined with the next key press.
pub fn character_map_and_combine_key(
app: &AndroidApp,
key_event: &KeyEvent<'_>,
combining_accent: &mut Option<char>,
) -> Option<KeyMapChar> {
let device_id = key_event.device_id();
let key_map = match app.device_key_character_map(device_id) {
Ok(key_map) => key_map,
Err(err) => {
log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
return None;
}
};
match key_map.get(key_event.key_code(), key_event.meta_state()) {
Ok(KeyMapChar::Unicode(unicode)) => {
// Only do dead key combining on key down
if key_event.action() == KeyAction::Down {
let combined_unicode = if let Some(accent) = combining_accent {
match key_map.get_dead_char(*accent, unicode) {
Ok(Some(key)) => Some(key),
Ok(None) => None,
Err(err) => {
log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
None
}
}
} else {
Some(unicode)
};
*combining_accent = None;
combined_unicode.map(KeyMapChar::Unicode)
} else {
Some(KeyMapChar::Unicode(unicode))
}
}
Ok(KeyMapChar::CombiningAccent(accent)) => {
if key_event.action() == KeyAction::Down {
*combining_accent = Some(accent);
}
Some(KeyMapChar::CombiningAccent(accent))
}
Ok(KeyMapChar::None) => {
// Leave any combining_accent state in tact (seems to match how other
// Android apps work)
None
}
Err(err) => {
log::warn!("KeyEvent: Failed to get key map character: {err:?}");
*combining_accent = None;
None
}
}
}
// TODO: We need to expose getUnicodeChar via android-activity instead of having
// a fixed mapping from key codes
pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key {
pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
use android_activity::input::Keycode::*;
match keycode {
Unknown => Key::Unidentified(native),
let native = NativeKey::Android(keycode.into());
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
match key_char {
Some(KeyMapChar::Unicode(c)) => Key::Character(smol_str::SmolStr::from_iter([c])),
Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)),
None | Some(KeyMapChar::None) => match keycode {
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::Named(NamedKey::BrowserHome),
Back => Key::Named(NamedKey::BrowserBack),
Call => Key::Named(NamedKey::Call),
Endcall => Key::Named(NamedKey::EndCall),
// Using `BrowserHome` instead of `GoHome` according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
Home => Key::BrowserHome,
Back => Key::BrowserBack,
Call => Key::Call,
Endcall => Key::EndCall,
//-------------------------------------------------------------------------------
// These should be redundant because they should have already been matched
// as `KeyMapChar::Unicode`, but also matched here as a fallback
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::Named(NamedKey::ArrowUp),
DpadDown => Key::Named(NamedKey::ArrowDown),
DpadLeft => Key::Named(NamedKey::ArrowLeft),
DpadRight => Key::Named(NamedKey::ArrowRight),
DpadCenter => Key::Named(NamedKey::Enter),
//-------------------------------------------------------------------------------
// Reporting unidentified, because the specific character is layout dependent.
// (I'm not sure though)
Keycode0 => Key::Character("0".into()),
Keycode1 => Key::Character("1".into()),
Keycode2 => Key::Character("2".into()),
Keycode3 => Key::Character("3".into()),
Keycode4 => Key::Character("4".into()),
Keycode5 => Key::Character("5".into()),
Keycode6 => Key::Character("6".into()),
Keycode7 => Key::Character("7".into()),
Keycode8 => Key::Character("8".into()),
Keycode9 => Key::Character("9".into()),
Star => Key::Character("*".into()),
Pound => Key::Character("#".into()),
A => Key::Character("a".into()),
B => Key::Character("b".into()),
C => Key::Character("c".into()),
D => Key::Character("d".into()),
E => Key::Character("e".into()),
F => Key::Character("f".into()),
G => Key::Character("g".into()),
H => Key::Character("h".into()),
I => Key::Character("i".into()),
J => Key::Character("j".into()),
K => Key::Character("k".into()),
L => Key::Character("l".into()),
M => Key::Character("m".into()),
N => Key::Character("n".into()),
O => Key::Character("o".into()),
P => Key::Character("p".into()),
Q => Key::Character("q".into()),
R => Key::Character("r".into()),
S => Key::Character("s".into()),
T => Key::Character("t".into()),
U => Key::Character("u".into()),
V => Key::Character("v".into()),
W => Key::Character("w".into()),
X => Key::Character("x".into()),
Y => Key::Character("y".into()),
Z => Key::Character("z".into()),
Comma => Key::Character(",".into()),
Period => Key::Character(".".into()),
Grave => Key::Character("`".into()),
Minus => Key::Character("-".into()),
Equals => Key::Character("=".into()),
LeftBracket => Key::Character("[".into()),
RightBracket => Key::Character("]".into()),
Backslash => Key::Character("\\".into()),
Semicolon => Key::Character(";".into()),
Apostrophe => Key::Character("'".into()),
Slash => Key::Character("/".into()),
At => Key::Character("@".into()),
Plus => Key::Character("+".into()),
//-------------------------------------------------------------------------------
DpadUp => Key::ArrowUp,
DpadDown => Key::ArrowDown,
DpadLeft => Key::ArrowLeft,
DpadRight => Key::ArrowRight,
DpadCenter => Key::Enter,
VolumeUp => Key::Named(NamedKey::AudioVolumeUp),
VolumeDown => Key::Named(NamedKey::AudioVolumeDown),
Power => Key::Named(NamedKey::Power),
Camera => Key::Named(NamedKey::Camera),
Clear => Key::Named(NamedKey::Clear),
VolumeUp => Key::AudioVolumeUp,
VolumeDown => Key::AudioVolumeDown,
Power => Key::Power,
Camera => Key::Camera,
Clear => Key::Clear,
AltLeft => Key::Named(NamedKey::Alt),
AltRight => Key::Named(NamedKey::Alt),
ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Named(NamedKey::Tab),
Space => Key::Named(NamedKey::Space),
Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::Named(NamedKey::LaunchMail),
Enter => Key::Named(NamedKey::Enter),
Del => Key::Named(NamedKey::Backspace),
AltLeft => Key::Alt,
AltRight => Key::Alt,
ShiftLeft => Key::Shift,
ShiftRight => Key::Shift,
Tab => Key::Tab,
Space => Key::Space,
Sym => Key::Symbol,
Explorer => Key::LaunchWebBrowser,
Envelope => Key::LaunchMail,
Enter => Key::Enter,
Del => Key::Backspace,
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Named(NamedKey::Alt),
// According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM
Num => Key::Alt,
Headsethook => Key::Named(NamedKey::HeadsetHook),
Focus => Key::Named(NamedKey::CameraFocus),
Headsethook => Key::HeadsetHook,
Focus => Key::CameraFocus,
Notification => Key::Named(NamedKey::Notification),
Search => Key::Named(NamedKey::BrowserSearch),
MediaPlayPause => Key::Named(NamedKey::MediaPlayPause),
MediaStop => Key::Named(NamedKey::MediaStop),
MediaNext => Key::Named(NamedKey::MediaTrackNext),
MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious),
MediaRewind => Key::Named(NamedKey::MediaRewind),
MediaFastForward => Key::Named(NamedKey::MediaFastForward),
Mute => Key::Named(NamedKey::MicrophoneVolumeMute),
PageUp => Key::Named(NamedKey::PageUp),
PageDown => Key::Named(NamedKey::PageDown),
Menu => Key::Unidentified(native),
Escape => Key::Named(NamedKey::Escape),
ForwardDel => Key::Named(NamedKey::Delete),
CtrlLeft => Key::Named(NamedKey::Control),
CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Named(NamedKey::Super),
Function => Key::Named(NamedKey::Fn),
Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Named(NamedKey::Pause),
MoveHome => Key::Named(NamedKey::Home),
MoveEnd => Key::Named(NamedKey::End),
Insert => Key::Named(NamedKey::Insert),
Forward => Key::Named(NamedKey::BrowserForward),
MediaPlay => Key::Named(NamedKey::MediaPlay),
MediaPause => Key::Named(NamedKey::MediaPause),
MediaClose => Key::Named(NamedKey::MediaClose),
MediaEject => Key::Named(NamedKey::Eject),
MediaRecord => Key::Named(NamedKey::MediaRecord),
F1 => Key::Named(NamedKey::F1),
F2 => Key::Named(NamedKey::F2),
F3 => Key::Named(NamedKey::F3),
F4 => Key::Named(NamedKey::F4),
F5 => Key::Named(NamedKey::F5),
F6 => Key::Named(NamedKey::F6),
F7 => Key::Named(NamedKey::F7),
F8 => Key::Named(NamedKey::F8),
F9 => Key::Named(NamedKey::F9),
F10 => Key::Named(NamedKey::F10),
F11 => Key::Named(NamedKey::F11),
F12 => Key::Named(NamedKey::F12),
NumLock => Key::Named(NamedKey::NumLock),
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Named(NamedKey::Enter),
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
Notification => Key::Notification,
Search => Key::BrowserSearch,
MediaPlayPause => Key::MediaPlayPause,
MediaStop => Key::MediaStop,
MediaNext => Key::MediaTrackNext,
MediaPrevious => Key::MediaTrackPrevious,
MediaRewind => Key::MediaRewind,
MediaFastForward => Key::MediaFastForward,
Mute => Key::MicrophoneVolumeMute,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
VolumeMute => Key::Named(NamedKey::AudioVolumeMute),
Info => Key::Named(NamedKey::Info),
ChannelUp => Key::Named(NamedKey::ChannelUp),
ChannelDown => Key::Named(NamedKey::ChannelDown),
ZoomIn => Key::Named(NamedKey::ZoomIn),
ZoomOut => Key::Named(NamedKey::ZoomOut),
Tv => Key::Named(NamedKey::TV),
Guide => Key::Named(NamedKey::Guide),
Dvr => Key::Named(NamedKey::DVR),
Bookmark => Key::Named(NamedKey::BrowserFavorites),
Captions => Key::Named(NamedKey::ClosedCaptionToggle),
Settings => Key::Named(NamedKey::Settings),
TvPower => Key::Named(NamedKey::TVPower),
TvInput => Key::Named(NamedKey::TVInput),
StbPower => Key::Named(NamedKey::STBPower),
StbInput => Key::Named(NamedKey::STBInput),
AvrPower => Key::Named(NamedKey::AVRPower),
AvrInput => Key::Named(NamedKey::AVRInput),
ProgRed => Key::Named(NamedKey::ColorF0Red),
ProgGreen => Key::Named(NamedKey::ColorF1Green),
ProgYellow => Key::Named(NamedKey::ColorF2Yellow),
ProgBlue => Key::Named(NamedKey::ColorF3Blue),
AppSwitch => Key::Named(NamedKey::AppSwitch),
LanguageSwitch => Key::Named(NamedKey::GroupNext),
MannerMode => Key::Named(NamedKey::MannerMode),
Keycode3dMode => Key::Named(NamedKey::TV3DMode),
Contacts => Key::Named(NamedKey::LaunchContacts),
Calendar => Key::Named(NamedKey::LaunchCalendar),
Music => Key::Named(NamedKey::LaunchMusicPlayer),
Calculator => Key::Named(NamedKey::LaunchApplication2),
ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku),
Eisu => Key::Named(NamedKey::Eisu),
Muhenkan => Key::Named(NamedKey::NonConvert),
Henkan => Key::Named(NamedKey::Convert),
KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana),
Kana => Key::Named(NamedKey::KanjiMode),
BrightnessDown => Key::Named(NamedKey::BrightnessDown),
BrightnessUp => Key::Named(NamedKey::BrightnessUp),
MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack),
Sleep => Key::Named(NamedKey::Standby),
Wakeup => Key::Named(NamedKey::WakeUp),
Pairing => Key::Named(NamedKey::Pairing),
MediaTopMenu => Key::Named(NamedKey::MediaTopMenu),
LastChannel => Key::Named(NamedKey::MediaLast),
TvDataService => Key::Named(NamedKey::TVDataService),
VoiceAssist => Key::Named(NamedKey::VoiceDial),
TvRadioService => Key::Named(NamedKey::TVRadioService),
TvTeletext => Key::Named(NamedKey::Teletext),
TvNumberEntry => Key::Named(NamedKey::TVNumberEntry),
TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog),
TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital),
TvSatellite => Key::Named(NamedKey::TVSatellite),
TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS),
TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS),
TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle),
TvNetwork => Key::Named(NamedKey::TVNetwork),
TvAntennaCable => Key::Named(NamedKey::TVAntennaCable),
TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1),
TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2),
TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3),
TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4),
TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1),
TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2),
TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1),
TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2),
TvInputVga1 => Key::Named(NamedKey::TVInputVGA1),
TvAudioDescription => Key::Named(NamedKey::TVAudioDescription),
TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp),
TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown),
TvZoomMode => Key::Named(NamedKey::ZoomToggle),
TvContentsMenu => Key::Named(NamedKey::TVContentsMenu),
TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext),
TvTimerProgramming => Key::Named(NamedKey::TVTimer),
Help => Key::Named(NamedKey::Help),
NavigatePrevious => Key::Named(NamedKey::NavigatePrevious),
NavigateNext => Key::Named(NamedKey::NavigateNext),
NavigateIn => Key::Named(NamedKey::NavigateIn),
NavigateOut => Key::Named(NamedKey::NavigateOut),
MediaSkipForward => Key::Named(NamedKey::MediaSkipForward),
MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward),
MediaStepForward => Key::Named(NamedKey::MediaStepForward),
MediaStepBackward => Key::Named(NamedKey::MediaStepBackward),
Cut => Key::Named(NamedKey::Cut),
Copy => Key::Named(NamedKey::Copy),
Paste => Key::Named(NamedKey::Paste),
Refresh => Key::Named(NamedKey::BrowserRefresh),
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Escape => Key::Escape,
ForwardDel => Key::Delete,
CtrlLeft => Key::Control,
CtrlRight => Key::Control,
CapsLock => Key::CapsLock,
ScrollLock => Key::ScrollLock,
MetaLeft => Key::Super,
MetaRight => Key::Super,
Function => Key::Fn,
Sysrq => Key::PrintScreen,
Break => Key::Pause,
MoveHome => Key::Home,
MoveEnd => Key::End,
Insert => Key::Insert,
Forward => Key::BrowserForward,
MediaPlay => Key::MediaPlay,
MediaPause => Key::MediaPause,
MediaClose => Key::MediaClose,
MediaEject => Key::Eject,
MediaRecord => Key::MediaRecord,
F1 => Key::F1,
F2 => Key::F2,
F3 => Key::F3,
F4 => Key::F4,
F5 => Key::F5,
F6 => Key::F6,
F7 => Key::F7,
F8 => Key::F8,
F9 => Key::F9,
F10 => Key::F10,
F11 => Key::F11,
F12 => Key::F12,
NumLock => Key::NumLock,
Numpad0 => Key::Character("0".into()),
Numpad1 => Key::Character("1".into()),
Numpad2 => Key::Character("2".into()),
Numpad3 => Key::Character("3".into()),
Numpad4 => Key::Character("4".into()),
Numpad5 => Key::Character("5".into()),
Numpad6 => Key::Character("6".into()),
Numpad7 => Key::Character("7".into()),
Numpad8 => Key::Character("8".into()),
Numpad9 => Key::Character("9".into()),
NumpadDivide => Key::Character("/".into()),
NumpadMultiply => Key::Character("*".into()),
NumpadSubtract => Key::Character("-".into()),
NumpadAdd => Key::Character("+".into()),
NumpadDot => Key::Character(".".into()),
NumpadComma => Key::Character(",".into()),
NumpadEnter => Key::Enter,
NumpadEquals => Key::Character("=".into()),
NumpadLeftParen => Key::Character("(".into()),
NumpadRightParen => Key::Character(")".into()),
// -----------------------------------------------------------------
// Keycodes that don't have a logical Key mapping
// -----------------------------------------------------------------
Unknown => Key::Unidentified(native),
VolumeMute => Key::AudioVolumeMute,
Info => Key::Info,
ChannelUp => Key::ChannelUp,
ChannelDown => Key::ChannelDown,
ZoomIn => Key::ZoomIn,
ZoomOut => Key::ZoomOut,
Tv => Key::TV,
Window => Key::Unidentified(native),
Guide => Key::Guide,
Dvr => Key::DVR,
Bookmark => Key::BrowserFavorites,
Captions => Key::ClosedCaptionToggle,
Settings => Key::Settings,
TvPower => Key::TVPower,
TvInput => Key::TVInput,
StbPower => Key::STBPower,
StbInput => Key::STBInput,
AvrPower => Key::AVRPower,
AvrInput => Key::AVRInput,
ProgRed => Key::ColorF0Red,
ProgGreen => Key::ColorF1Green,
ProgYellow => Key::ColorF2Yellow,
ProgBlue => Key::ColorF3Blue,
AppSwitch => Key::AppSwitch,
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
LanguageSwitch => Key::GroupNext,
MannerMode => Key::MannerMode,
Keycode3dMode => Key::TV3DMode,
Contacts => Key::LaunchContacts,
Calendar => Key::LaunchCalendar,
Music => Key::LaunchMusicPlayer,
Calculator => Key::LaunchApplication2,
ZenkakuHankaku => Key::ZenkakuHankaku,
Eisu => Key::Eisu,
Muhenkan => Key::NonConvert,
Henkan => Key::Convert,
KatakanaHiragana => Key::HiraganaKatakana,
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Kana => Key::KanjiMode,
Assist => Key::Unidentified(native),
BrightnessDown => Key::BrightnessDown,
BrightnessUp => Key::BrightnessUp,
MediaAudioTrack => Key::MediaAudioTrack,
Sleep => Key::Standby,
Wakeup => Key::WakeUp,
Pairing => Key::Pairing,
MediaTopMenu => Key::MediaTopMenu,
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
LastChannel => Key::MediaLast,
TvDataService => Key::TVDataService,
VoiceAssist => Key::VoiceDial,
TvRadioService => Key::TVRadioService,
TvTeletext => Key::Teletext,
TvNumberEntry => Key::TVNumberEntry,
TvTerrestrialAnalog => Key::TVTerrestrialAnalog,
TvTerrestrialDigital => Key::TVTerrestrialDigital,
TvSatellite => Key::TVSatellite,
TvSatelliteBs => Key::TVSatelliteBS,
TvSatelliteCs => Key::TVSatelliteCS,
TvSatelliteService => Key::TVSatelliteToggle,
TvNetwork => Key::TVNetwork,
TvAntennaCable => Key::TVAntennaCable,
TvInputHdmi1 => Key::TVInputHDMI1,
TvInputHdmi2 => Key::TVInputHDMI2,
TvInputHdmi3 => Key::TVInputHDMI3,
TvInputHdmi4 => Key::TVInputHDMI4,
TvInputComposite1 => Key::TVInputComposite1,
TvInputComposite2 => Key::TVInputComposite2,
TvInputComponent1 => Key::TVInputComponent1,
TvInputComponent2 => Key::TVInputComponent2,
TvInputVga1 => Key::TVInputVGA1,
TvAudioDescription => Key::TVAudioDescription,
TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp,
TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown,
TvZoomMode => Key::ZoomToggle,
TvContentsMenu => Key::TVContentsMenu,
TvMediaContextMenu => Key::TVMediaContext,
TvTimerProgramming => Key::TVTimer,
Help => Key::Help,
NavigatePrevious => Key::NavigatePrevious,
NavigateNext => Key::NavigateNext,
NavigateIn => Key::NavigateIn,
NavigateOut => Key::NavigateOut,
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
MediaSkipForward => Key::MediaSkipForward,
MediaSkipBackward => Key::MediaSkipBackward,
MediaStepForward => Key::MediaStepForward,
MediaStepBackward => Key::MediaStepBackward,
SoftSleep => Key::Unidentified(native),
Cut => Key::Cut,
Copy => Key::Copy,
Paste => Key::Paste,
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
Refresh => Key::BrowserRefresh,
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
// Can be added on demand
SoftLeft => Key::Unidentified(native),
SoftRight => Key::Unidentified(native),
Menu => Key::Unidentified(native),
Pictsymbols => Key::Unidentified(native),
SwitchCharset => Key::Unidentified(native),
// -----------------------------------------------------------------
// Gamepad events should be exposed through a separate API, not
// keyboard events
ButtonA => Key::Unidentified(native),
ButtonB => Key::Unidentified(native),
ButtonC => Key::Unidentified(native),
ButtonX => Key::Unidentified(native),
ButtonY => Key::Unidentified(native),
ButtonZ => Key::Unidentified(native),
ButtonL1 => Key::Unidentified(native),
ButtonR1 => Key::Unidentified(native),
ButtonL2 => Key::Unidentified(native),
ButtonR2 => Key::Unidentified(native),
ButtonThumbl => Key::Unidentified(native),
ButtonThumbr => Key::Unidentified(native),
ButtonStart => Key::Unidentified(native),
ButtonSelect => Key::Unidentified(native),
ButtonMode => Key::Unidentified(native),
// -----------------------------------------------------------------
Window => Key::Unidentified(native),
Button1 => Key::Unidentified(native),
Button2 => Key::Unidentified(native),
Button3 => Key::Unidentified(native),
Button4 => Key::Unidentified(native),
Button5 => Key::Unidentified(native),
Button6 => Key::Unidentified(native),
Button7 => Key::Unidentified(native),
Button8 => Key::Unidentified(native),
Button9 => Key::Unidentified(native),
Button10 => Key::Unidentified(native),
Button11 => Key::Unidentified(native),
Button12 => Key::Unidentified(native),
Button13 => Key::Unidentified(native),
Button14 => Key::Unidentified(native),
Button15 => Key::Unidentified(native),
Button16 => Key::Unidentified(native),
Yen => Key::Unidentified(native),
Ro => Key::Unidentified(native),
Assist => Key::Unidentified(native),
Keycode11 => Key::Unidentified(native),
Keycode12 => Key::Unidentified(native),
StemPrimary => Key::Unidentified(native),
Stem1 => Key::Unidentified(native),
Stem2 => Key::Unidentified(native),
Stem3 => Key::Unidentified(native),
DpadUpLeft => Key::Unidentified(native),
DpadDownLeft => Key::Unidentified(native),
DpadUpRight => Key::Unidentified(native),
DpadDownRight => Key::Unidentified(native),
SoftSleep => Key::Unidentified(native),
SystemNavigationUp => Key::Unidentified(native),
SystemNavigationDown => Key::Unidentified(native),
SystemNavigationLeft => Key::Unidentified(native),
SystemNavigationRight => Key::Unidentified(native),
AllApps => Key::Unidentified(native),
ThumbsUp => Key::Unidentified(native),
ThumbsDown => Key::Unidentified(native),
ProfileSwitch => Key::Unidentified(native),
// It's always possible that new versions of Android could introduce
// key codes we can't know about at compile time.
_ => Key::Unidentified(native),
},
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ use std::{
mem,
os::raw::c_void,
ptr,
sync::{Arc, Mutex},
time::Instant,
};
@@ -15,22 +16,21 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::foundation::{CGRect, CGSize, NSInteger, NSProcessInfo};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use icrate::Foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView;
use super::view::WinitUIWindow;
use crate::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::NSOperatingSystemVersion,
},
window::WindowId as RootWindowId,
};
@@ -46,6 +46,19 @@ macro_rules! bug_assert {
};
}
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> {
Success {
event_handler: Box<dyn EventHandler>,
@@ -57,9 +70,15 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event<'static, Never> {
impl Event<Never> {
fn is_redraw(&self) -> bool {
matches!(self, Event::RedrawRequested(_))
matches!(
self,
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
}
)
}
}
@@ -68,25 +87,25 @@ impl Event<'static, Never> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
Launching {
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_windows: Vec<Id<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingEvents {
event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
},
ProcessingRedraws {
event_handler: Box<dyn EventHandler>,
@@ -102,7 +121,7 @@ enum AppStateImpl {
Terminated,
}
struct AppState {
pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
@@ -110,24 +129,18 @@ struct AppState {
}
impl AppState {
// requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> {
pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
if cfg!(debug_assertions) {
assert_main_thread!(
"bug in winit: `AppState::get_mut()` can only be called on the main thread"
);
}
let mut guard = APP_STATE.borrow_mut();
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
#[cold]
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain());
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(),
@@ -138,7 +151,7 @@ impl AppState {
waker,
});
}
init_guard(&mut guard)
init_guard(&mut guard);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
}
@@ -204,9 +217,7 @@ impl AppState {
});
}
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
@@ -223,7 +234,7 @@ impl AppState {
};
self.set_state(AppStateImpl::ProcessingEvents {
event_handler,
active_control_flow: ControlFlow::Poll,
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
(windows, events)
@@ -279,7 +290,6 @@ impl AppState {
};
(waiting_event_handler, event)
}
(ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"),
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@@ -363,7 +373,7 @@ impl AppState {
}
}
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents {
event_handler,
@@ -393,9 +403,6 @@ impl AppState {
let new = self.control_flow;
match (old, new) {
(ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
}),
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
@@ -428,36 +435,34 @@ impl AppState {
});
self.waker.start_at(new_instant)
}
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished {
waiting_event_handler,
});
self.waker.start()
}
(_, ControlFlow::ExitWithCode(_)) => {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
self.control_flow = old
}
}
}
fn terminated_transition(&mut self) -> Box<dyn EventHandler> {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler,
s => bug!(
"`LoopDestroyed` happened while not processing events {:?}",
s
),
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
}
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
}
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
@@ -477,10 +482,8 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
window.makeKeyAndVisible();
}
// requires main thread and window is a UIWindow
// retains window
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
let mut this = AppState::get_mut();
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_gpu_redraws,
@@ -509,24 +512,17 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>)
}
}
// requires main thread
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut().will_launch_transition(queued_event_handler)
pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
}
// requires main thread
pub unsafe fn did_finish_launching() {
let mut this = AppState::get_mut();
pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s),
};
// start waking up the event loop now!
bug_assert!(
this.control_flow == ControlFlow::Poll,
"unexpectedly not setup to `Poll` on launch!"
);
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
@@ -544,7 +540,7 @@ pub unsafe fn did_finish_launching() {
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<Object>()];
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen);
let controller = window.rootViewController();
@@ -554,13 +550,13 @@ pub unsafe fn did_finish_launching() {
window.makeKeyAndVisible();
}
let (windows, events) = AppState::get_mut().did_finish_launching_transition();
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
handle_nonuser_events(events);
handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching
@@ -569,27 +565,27 @@ pub unsafe fn did_finish_launching() {
}
}
// requires main thread
// AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() {
let mut this = AppState::get_mut();
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() {
None => return,
Some(wakeup_event) => wakeup_event,
};
drop(this);
handle_nonuser_event(wakeup_event)
handle_nonuser_event(mtm, wakeup_event)
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event))
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
handle_nonuser_events(mtm, std::iter::once(event))
}
// requires main thread
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut();
pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker,
events: I,
) {
let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
@@ -602,7 +598,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
processing_redraws,
} => (event_handler, active_control_flow, processing_redraws),
};
let mut control_flow = this.control_flow;
drop(this);
for wrapper in events {
@@ -616,16 +611,16 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
event_handler.handle_nonuser_event(event)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
loop {
let mut this = AppState::get_mut();
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
@@ -657,7 +652,6 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
active_control_flow,
}
});
this.control_flow = control_flow;
break;
}
drop(this);
@@ -673,20 +667,18 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
event
);
}
event_handler.handle_nonuser_event(event, &mut control_flow)
event_handler.handle_nonuser_event(event)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
}
}
// requires main thread
unsafe fn handle_user_events() {
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow;
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
@@ -703,10 +695,10 @@ unsafe fn handle_user_events() {
}
drop(this);
event_handler.handle_user_events(&mut control_flow);
event_handler.handle_user_events();
loop {
let mut this = AppState::get_mut();
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback {
ref mut queued_events,
@@ -727,28 +719,24 @@ unsafe fn handle_user_events() {
queued_gpu_redraws,
active_control_flow,
});
this.control_flow = control_flow;
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
event_handler.handle_nonuser_event(event, &mut control_flow)
}
EventWrapper::EventProxy(proxy) => {
handle_event_proxy(&mut event_handler, control_flow, proxy)
EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event),
EventWrapper::ScaleFactorChanged(event) => {
handle_hidpi_proxy(&mut event_handler, event)
}
}
}
event_handler.handle_user_events(&mut control_flow);
event_handler.handle_user_events();
}
}
// requires main thread
pub unsafe fn handle_main_events_cleared() {
let mut this = AppState::get_mut();
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() {
return;
}
@@ -758,84 +746,62 @@ pub unsafe fn handle_main_events_cleared() {
};
drop(this);
// User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
handle_user_events(mtm);
let mut this = AppState::get_mut();
let mut redraw_events: Vec<EventWrapper> = this
let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
})
})
.collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
drop(this);
handle_nonuser_events(redraw_events);
handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
}
// requires main thread
pub unsafe fn handle_events_cleared() {
AppState::get_mut().events_cleared_transition();
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
// requires main thread
pub unsafe fn terminated() {
let mut this = AppState::get_mut();
pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let mut event_handler = this.terminated_transition();
let mut control_flow = this.control_flow;
drop(this);
event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow)
event_handler.handle_nonuser_event(Event::LoopExiting)
}
fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
control_flow: ControlFlow,
proxy: EventProxy,
) {
match proxy {
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window,
),
}
}
fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
window: Id<WinitUIWindow, Shared>,
) {
let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size;
fn handle_hidpi_proxy(event_handler: &mut Box<dyn EventHandler>, event: ScaleFactorChanged) {
let ScaleFactorChanged {
suggested_size,
scale_factor,
window,
} = event;
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
event_handler.handle_nonuser_event(event, &mut control_flow);
event_handler.handle_nonuser_event(event);
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size;
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView, Shared>, CGRect) {
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
@@ -924,7 +890,7 @@ macro_rules! os_capabilities {
impl From<NSOperatingSystemVersion> for OSCapabilities {
fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities {
$(let $name = os_version.meets_requirements($major, $minor);)*
$(let $name = meets_requirements(os_version, $major, $minor);)*
OSCapabilities { $($name,)* os_version, }
}
}
@@ -934,7 +900,7 @@ macro_rules! os_capabilities {
pub fn $error_name(&self, extra_msg: &str) {
log::warn!(
concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"),
$major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch,
$major, $minor, self.os_version.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion,
extra_msg
)
}
@@ -962,16 +928,18 @@ os_capabilities! {
force_touch: 9-0,
}
impl NSOperatingSystemVersion {
fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool {
(self.major, self.minor) >= (required_major, required_minor)
}
fn meets_requirements(
version: NSOperatingSystemVersion,
required_major: NSInteger,
required_minor: NSInteger,
) -> bool {
(version.majorVersion, version.minorVersion) >= (required_major, required_minor)
}
pub fn os_capabilities() -> OSCapabilities {
static OS_CAPABILITIES: Lazy<OSCapabilities> = Lazy::new(|| {
let version: NSOperatingSystemVersion = unsafe {
let process_info = NSProcessInfo::process_info();
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
@@ -984,7 +952,7 @@ pub fn os_capabilities() -> OSCapabilities {
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
msg_send![&process_info, operatingSystemVersion]
process_info.operatingSystemVersion()
};
version.into()
});

View File

@@ -14,60 +14,82 @@ use core_foundation::runloop::{
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::foundation::{MainThreadMarker, NSString};
use objc2::rc::{Id, Shared};
use icrate::Foundation::{MainThreadMarker, NSString};
use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle};
use crate::{
dpi::LogicalSize,
error::EventLoopError,
event::Event,
event_loop::{
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootEventLoopWindowTarget,
},
platform::ios::Idiom,
};
use super::{app_state, monitor, view, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
};
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<'static, Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow, Shared>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>,
sender_to_clone: Sender<T>,
pub(super) mtm: MainThreadMarker,
p: PhantomData<T>,
}
impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap())
monitor::uiscreens(self.mtm)
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
}
pub(crate) fn exit(&self) {
// https://developer.apple.com/library/archive/qa/qa1561/_index.html
// it is not possible to quit an iOS app gracefully and programatically
warn!("`ControlFlow::Exit` ignored on iOS");
}
pub(crate) fn exiting(&self) -> bool {
false
}
}
pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget<T>,
}
@@ -75,8 +97,11 @@ pub struct EventLoop<T: 'static> {
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false;
unsafe {
@@ -88,38 +113,50 @@ impl<T: 'static> EventLoop<T> {
SINGLETON_INIT = true;
}
let (sender_to_clone, receiver) = mpsc::channel();
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
EventLoop {
Ok(EventLoop {
mtm,
sender,
receiver,
window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget {
receiver,
sender_to_clone,
mtm,
p: PhantomData,
},
_marker: PhantomData,
},
}
})
}
pub fn run<F>(self, event_handler: F) -> !
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
unsafe {
let application = UIApplication::shared(MainThreadMarker::new().unwrap());
let application = UIApplication::shared(self.mtm);
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
);
app_state::will_launch(Box::new(EventLoopHandler {
let event_handler = std::mem::transmute::<
Box<dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>)>,
Box<EventHandlerCallback<T>>,
>(Box::new(event_handler));
let handler = EventLoopHandler {
f: event_handler,
receiver: self.receiver,
event_loop: self.window_target,
}));
};
app_state::will_launch(self.mtm, Box::new(handler));
// Ensure application delegate is initialized
view::WinitApplicationDelegate::class();
@@ -135,7 +172,7 @@ impl<T: 'static> EventLoop<T> {
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
EventLoopProxy::new(self.sender.clone())
}
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
@@ -146,9 +183,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
UIDevice::current(MainThreadMarker::new().unwrap())
.userInterfaceIdiom()
.into()
UIDevice::current(self.mtm).userInterfaceIdiom().into()
}
}
@@ -226,21 +261,20 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
@@ -251,13 +285,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -267,13 +300,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity,
_: *mut c_void,
) {
unsafe {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -314,17 +346,20 @@ fn setup_control_flow_observers() {
#[derive(Debug)]
pub enum Never {}
type EventHandlerCallback<T> = dyn FnMut(Event<T>, &RootEventLoopWindowTarget<T>) + 'static;
pub trait EventHandler: Debug {
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
fn handle_nonuser_event(&mut self, event: Event<Never>);
fn handle_user_events(&mut self);
}
struct EventLoopHandler<F, T: 'static> {
f: F,
struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>,
}
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
impl<T: 'static> Debug for EventLoopHandler<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("event_loop", &self.event_loop)
@@ -332,22 +367,14 @@ impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
}
}
impl<F, T> EventHandler for EventLoopHandler<F, T>
where
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
T: 'static,
{
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
(self.f)(
event.map_nonuser_event().unwrap(),
&self.event_loop,
control_flow,
);
impl<T: 'static> EventHandler for EventLoopHandler<T> {
fn handle_nonuser_event(&mut self, event: Event<Never>) {
(self.f)(event.map_nonuser_event().unwrap(), &self.event_loop);
}
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
fn handle_user_events(&mut self) {
for event in self.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop);
}
}
}

View File

@@ -2,30 +2,11 @@
use std::convert::TryInto;
use icrate::Foundation::{NSInteger, NSUInteger};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSUInteger};
use crate::platform::ios::{Idiom, ScreenEdge};
#[repr(C)]
#[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion {
pub major: NSInteger,
pub minor: NSInteger,
pub patch: NSInteger,
}
unsafe impl Encode for NSOperatingSystemVersion {
const ENCODING: Encoding = Encoding::Struct(
"NSOperatingSystemVersion",
&[
NSInteger::ENCODING,
NSInteger::ENCODING,
NSInteger::ENCODING,
],
);
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
@@ -70,6 +51,10 @@ impl From<UIUserInterfaceIdiom> for Idiom {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
impl UIRectEdge {
pub(crate) const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -48,27 +48,16 @@
//!
//! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended
//! - applicationWillTerminate is LoopDestroyed
//! - applicationWillTerminate is LoopExiting
//!
//! Keep in mind that after LoopDestroyed event is received every attempt to draw with
//! Keep in mind that after LoopExiting event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed.
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !::objc2::foundation::is_main_thread() {
panic!($($t)*);
}
};
}
mod app_state;
mod event_loop;
mod ffi;
@@ -89,7 +78,7 @@ pub(crate) use self::{
use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
pub(crate) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId {

View File

@@ -2,12 +2,12 @@
use std::{
collections::{BTreeSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
fmt, hash, ptr,
};
use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::rc::{Id, Shared};
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable;
use objc2::rc::Id;
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
@@ -16,32 +16,59 @@ use crate::{
platform_impl::platform::app_state,
};
// TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(
self.0
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
}
}
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)).hash(state);
}
}
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
}
}
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode {
pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: ScreenModeSendSync,
screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle,
}
impl VideoMode {
fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
fn new(
uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
mtm: MainThreadMarker,
) -> VideoMode {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size();
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate_millihertz,
screen_mode: ScreenModeSendSync(screen_mode),
screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen),
}
}
@@ -61,18 +88,40 @@ impl VideoMode {
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen, Shared>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle {
inner: Inner,
ui_screen: MainThreadBound<Id<UIScreen>>,
}
impl Clone for MonitorHandle {
fn clone(&self) -> Self {
Self {
ui_screen: self
.ui_screen
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
}
}
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
@@ -86,31 +135,6 @@ impl Ord for MonitorHandle {
}
}
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Drop for MonitorHandle {
fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API
@@ -135,64 +159,80 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
// Holding `Id<UIScreen>` implies we're on the main thread.
let mtm = MainThreadMarker::new().unwrap();
Self {
inner: Inner { uiscreen },
ui_screen: MainThreadBound::new(ui_screen, mtm),
}
}
}
impl Inner {
pub fn name(&self) -> Option<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap());
if self.uiscreen == main {
Some("Primary".to_string())
} else if self.uiscreen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(MainThreadMarker::new().unwrap())
.iter()
.position(|rhs| rhs == &*self.uiscreen)
.map(|idx| idx.to_string())
}
self.ui_screen.get_on_main(|ui_screen, mtm| {
let main = UIScreen::main(mtm);
if *ui_screen == main {
Some("Primary".to_string())
} else if *ui_screen == main.mirroredScreen() {
Some("Mirrored".to_string())
} else {
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**ui_screen)
.map(|idx| idx.to_string())
}
})
}
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.uiscreen.nativeBounds();
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.uiscreen.nativeBounds();
let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
self.uiscreen.nativeScale() as f64
self.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(&self.uiscreen))
Some(
self.ui_screen
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
)
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode
let modes: BTreeSet<_> = self
.uiscreen
.availableModes()
.into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
self.ui_screen.get_on_main(|ui_screen, mtm| {
// Use Ord impl of RootVideoMode
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
}
})
.collect();
let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
modes.into_iter().map(|mode| mode.video_mode)
})
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoMode {
self.ui_screen.get_on_main(|ui_screen, mtm| {
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
})
}
}
@@ -220,27 +260,9 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
refresh_rate_millihertz as u32 * 1000
}
// MonitorHandleExtIOS
impl Inner {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(|screen| {
let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
MonitorHandle::new(screen)
})
.map(MonitorHandle::new)
.collect()
}

View File

@@ -1,6 +1,6 @@
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIWindow};
@@ -11,20 +11,21 @@ extern_class!(
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
unsafe { msg_send_id![self, windows] }
}
#[sel(statusBarFrame)]
#[method(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,6 +1,6 @@
use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::{MainThreadMarker, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
@@ -10,16 +10,17 @@ extern_class!(
unsafe impl ClassType for UIDevice {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[sel(userInterfaceIdiom)]
#[method(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UIEvent {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,10 +1,9 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use std::os::raw::{c_char, c_int};
use objc2::foundation::NSString;
use icrate::Foundation::NSString;
mod application;
mod coordinate_space;
@@ -13,6 +12,7 @@ mod event;
mod responder;
mod screen;
mod screen_mode;
mod status_bar_style;
mod touch;
mod trait_collection;
mod view;
@@ -26,6 +26,7 @@ pub(crate) use self::event::UIEvent;
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::status_bar_style::UIStatusBarStyle;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]

View File

@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use icrate::Foundation::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,5 +7,6 @@ extern_class!(
unsafe impl ClassType for UIResponder {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
@@ -11,53 +11,54 @@ extern_class!(
unsafe impl ClassType for UIScreen {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(bounds)]
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(scale)]
#[method(scale)]
pub fn scale(&self) -> CGFloat;
#[sel(nativeBounds)]
#[method(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[sel(nativeScale)]
#[method(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[sel(maximumFramesPerSecond)]
#[method(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
pub fn mirroredScreen(&self) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[sel(setCurrentMode:)]
#[method(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
unsafe { msg_send_id![self, availableModes] }
}
#[sel(setOverscanCompensation:)]
#[method(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}

View File

@@ -1,5 +1,5 @@
use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use icrate::Foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -7,12 +7,13 @@ extern_class!(
unsafe impl ClassType for UIScreenMode {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIScreenMode {
#[sel(size)]
#[method(size)]
pub fn size(&self) -> CGSize;
}
);

View File

@@ -0,0 +1,27 @@
use crate::platform::ios::StatusBarStyle;
use icrate::Foundation::NSInteger;
use objc2::encode::{Encode, Encoding};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIStatusBarStyle {
#[default]
Default = 0,
LightContent = 1,
DarkContent = 3,
}
impl From<StatusBarStyle> for UIStatusBarStyle {
fn from(value: StatusBarStyle) -> Self {
match value {
StatusBarStyle::Default => Self::Default,
StatusBarStyle::LightContent => Self::LightContent,
StatusBarStyle::DarkContent => Self::DarkContent,
}
}
}
unsafe impl Encode for UIStatusBarStyle {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::UIView;
@@ -10,27 +10,28 @@ extern_class!(
unsafe impl ClassType for UITouch {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITouch {
#[sel(locationInView:)]
#[method(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[sel(type)]
#[method(type)]
pub fn type_(&self) -> UITouchType;
#[sel(force)]
#[method(force)]
pub fn force(&self) -> CGFloat;
#[sel(maximumPossibleForce)]
#[method(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[sel(altitudeAngle)]
#[method(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[sel(phase)]
#[method(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);

View File

@@ -1,6 +1,6 @@
use icrate::Foundation::{NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -8,12 +8,13 @@ extern_class!(
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[sel(forceTouchCapability)]
#[method(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{CGFloat, CGRect, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIResponder, UIViewController};
@@ -12,57 +12,58 @@ extern_class!(
unsafe impl ClassType for UIView {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIView {
#[sel(bounds)]
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(setBounds:)]
#[method(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[sel(frame)]
#[method(frame)]
pub fn frame(&self) -> CGRect;
#[sel(setFrame:)]
#[method(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[sel(contentScaleFactor)]
#[method(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[sel(setContentScaleFactor:)]
#[method(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[sel(setMultipleTouchEnabled:)]
#[method(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[sel(setRootViewController:)]
#[method(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[sel(convertRect:toCoordinateSpace:)]
#[method(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(convertRect:fromCoordinateSpace:)]
#[method(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(safeAreaInsets)]
#[method(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[sel(setNeedsDisplay)]
#[method(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
}
);

View File

@@ -1,7 +1,7 @@
use icrate::Foundation::{NSObject, NSUInteger};
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSObject, NSUInteger};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIView};
@@ -12,28 +12,29 @@ extern_class!(
unsafe impl ClassType for UIViewController {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIViewController {
#[sel(attemptRotationToDeviceOrientation)]
#[method(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[sel(setNeedsStatusBarAppearanceUpdate)]
#[method(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView, Shared>> {
pub fn view(&self) -> Option<Id<UIView>> {
unsafe { msg_send_id![self, view] }
}
#[sel(setView:)]
#[method(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);

View File

@@ -1,6 +1,6 @@
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use icrate::Foundation::NSObject;
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIScreen, UIView};
@@ -11,25 +11,26 @@ extern_class!(
unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen, Shared> {
pub fn screen(&self) -> Id<UIScreen> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setScreen:)]
#[method(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[sel(setHidden:)]
#[method(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[sel(makeKeyAndVisible)]
#[method(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
#[sel(isKeyWindow)]
#[method(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool;
}
);

View File

@@ -1,14 +1,18 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr::NonNull;
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::rc::{Id, Shared};
use objc2::runtime::Class;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType};
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet};
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::app_state::{self, EventWrapper};
use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
UIWindow,
UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView,
UIViewController, UIWindow,
};
use super::window::WindowId;
use crate::{
@@ -16,8 +20,6 @@ use crate::{
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations,
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen,
@@ -26,33 +28,33 @@ use crate::{
};
declare_class!(
pub(crate) struct WinitView {}
pub(crate) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIView";
}
unsafe impl WinitView {
#[sel(drawRect:)]
#[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap();
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
}
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
}),
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
#[sel(layoutSubviews)]
#[method(layoutSubviews)]
fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let window = self.window().unwrap();
@@ -75,16 +77,18 @@ declare_class!(
self.setFrame(window_bounds);
}
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size),
}));
}
}),
);
}
#[sel(setContentScaleFactor:)]
#[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap();
let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
@@ -112,43 +116,44 @@ declare_class!(
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
#[sel(touchesBegan:withEvent:)]
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesMoved:withEvent:)]
#[method(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesEnded:withEvent:)]
#[method(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
#[sel(touchesCancelled:withEvent:)]
#[method(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
}
@@ -158,17 +163,17 @@ declare_class!(
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitView {
fn window(&self) -> Option<Id<WinitUIWindow, Shared>> {
fn window(&self) -> Option<Id<WinitUIWindow>> {
unsafe { msg_send_id![self, window] }
}
unsafe fn traitCollection(&self) -> Id<UITraitCollection, Shared> {
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
msg_send_id![self, traitCollection]
}
// TODO: Allow the user to customize this
#[sel(layerClass)]
pub(crate) fn layerClass() -> &'static Class;
#[method(layerClass)]
pub(crate) fn layerClass() -> &'static AnyClass;
}
);
@@ -178,9 +183,8 @@ impl WinitView {
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
@@ -203,7 +207,9 @@ impl WinitView {
let trait_collection = unsafe { self.traitCollection() };
let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
if touch_capability == UIForceTouchCapability::Available
|| touch_type == UITouchType::Pencil
{
let force = touch.force();
let max_possible_force = touch.maximumPossibleForce();
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
@@ -254,108 +260,125 @@ impl WinitView {
}),
}));
}
unsafe {
app_state::handle_nonuser_events(touch_events);
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, touch_events);
}
}
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
preferred_status_bar_style: Cell<UIStatusBarStyle>,
prefers_home_indicator_auto_hidden: Cell<bool>,
supported_orientations: Cell<UIInterfaceOrientationMask>,
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
declare_class!(
pub(crate) struct WinitViewController {
_prefers_status_bar_hidden: bool,
_prefers_home_indicator_auto_hidden: bool,
_supported_orientations: UIInterfaceOrientationMask,
_preferred_screen_edges_deferring_system_gestures: UIRectEdge,
state: IvarDrop<Box<ViewControllerState>, "_state">,
}
mod view_controller_ivars;
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIViewController";
}
unsafe impl WinitViewController {
#[sel(shouldAutorotate)]
#[method(init)]
unsafe fn init(this: *mut Self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = msg_send![super(this), init];
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
}
unsafe impl WinitViewController {
#[method(shouldAutorotate)]
fn should_autorotate(&self) -> bool {
true
}
}
unsafe impl WinitViewController {
#[sel(prefersStatusBarHidden)]
#[method(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool {
*self._prefers_status_bar_hidden
self.state.prefers_status_bar_hidden.get()
}
#[sel(setPrefersStatusBarHidden:)]
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
*self._prefers_status_bar_hidden = val;
self.setNeedsStatusBarAppearanceUpdate();
#[method(preferredStatusBarStyle)]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
self.state.preferred_status_bar_style.get()
}
#[sel(prefersHomeIndicatorAutoHidden)]
#[method(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
*self._prefers_home_indicator_auto_hidden
self.state.prefers_home_indicator_auto_hidden.get()
}
#[sel(setPrefersHomeIndicatorAutoHidden:)]
fn set_prefers_home_indicator_auto_hidden(&mut self, val: bool) {
*self._prefers_home_indicator_auto_hidden = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
#[sel(supportedInterfaceOrientations)]
#[method(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
*self._supported_orientations
self.state.supported_orientations.get()
}
#[sel(setSupportedInterfaceOrientations:)]
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
*self._supported_orientations = val;
UIViewController::attemptRotationToDeviceOrientation();
}
#[sel(preferredScreenEdgesDeferringSystemGestures)]
#[method(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
*self._preferred_screen_edges_deferring_system_gestures
self.state
.preferred_screen_edges_deferring_system_gestures
.get()
}
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
fn set_preferred_screen_edges_deferring_system_gestures(&mut self, val: UIRectEdge) {
*self._preferred_screen_edges_deferring_system_gestures = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitViewController {
#[sel(setPrefersStatusBarHidden:)]
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
#[sel(setSupportedInterfaceOrientations:)]
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
#[sel(setPrefersHomeIndicatorAutoHidden:)]
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
}
);
impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.state.prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) {
self.state.preferred_status_bar_style.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.state
.preferred_screen_edges_deferring_system_gestures
.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
pub(crate) fn set_supported_interface_orientations(
&self,
mtm: MainThreadMarker,
@@ -378,7 +401,8 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown
}
};
self.setSupportedInterfaceOrientations(mask);
self.state.supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
}
pub(crate) fn new(
@@ -386,17 +410,20 @@ impl WinitViewController {
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), init] };
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden);
this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into());
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden);
this.set_prefers_home_indicator_auto_hidden(
platform_attributes.prefers_home_indicator_hidden,
);
this.setPreferredScreenEdgesDeferringSystemGestures(
this.set_preferred_screen_edges_deferring_system_gestures(
platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into(),
@@ -410,33 +437,39 @@ impl WinitViewController {
declare_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow {}
pub(crate) struct WinitUIWindow;
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitUIWindow";
}
unsafe impl WinitUIWindow {
#[sel(becomeKeyWindow)]
#[method(becomeKeyWindow)]
fn become_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true),
}));
}
}),
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
#[sel(resignKeyWindow)]
#[method(resignKeyWindow)]
fn resign_key_window(&self) {
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false),
}));
}
}),
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
}
@@ -444,26 +477,25 @@ declare_class!(
impl WinitUIWindow {
pub(crate) fn new(
_mtm: MainThreadMarker,
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
match window_attributes.fullscreen.clone().map(Into::into) {
match window_attributes.fullscreen.0.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor();
let screen = monitor.ui_screen();
screen.setCurrentMode(Some(&video_mode.screen_mode.0));
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
}
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen();
let screen = monitor.ui_screen(mtm);
this.setScreen(screen);
}
_ => (),
@@ -478,38 +510,45 @@ impl WinitUIWindow {
}
declare_class!(
pub struct WinitApplicationDelegate {}
pub struct WinitApplicationDelegate;
unsafe impl ClassType for WinitApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
// UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate {
#[sel(application:didFinishLaunchingWithOptions:)]
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
unsafe {
app_state::did_finish_launching();
}
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[sel(applicationDidBecomeActive:)]
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[sel(applicationWillResignActive:)]
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[sel(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, _application: &UIApplication) {}
#[sel(applicationDidEnterBackground:)]
fn did_enter_background(&self, _application: &UIApplication) {}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
}
#[sel(applicationWillTerminate:)]
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
for window in application.windows().iter() {
@@ -526,10 +565,37 @@ declare_class!(
}));
}
}
unsafe {
app_state::handle_nonuser_events(events);
app_state::terminated();
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);
impl WinitApplicationDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
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

@@ -1,17 +1,13 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::VecDeque,
ffi::c_void,
ops::{Deref, DerefMut},
};
use std::collections::VecDeque;
use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{
@@ -19,12 +15,9 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, ValidOrientations},
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -33,9 +26,9 @@ use crate::{
};
pub struct Inner {
pub(crate) window: Id<WinitUIWindow, Shared>,
pub(crate) view_controller: Id<WinitViewController, Shared>,
pub(crate) view: Id<WinitView, Shared>,
window: Id<WinitUIWindow>,
view_controller: Id<WinitViewController>,
view: Id<WinitView>,
gl_or_metal_backed: bool,
}
@@ -48,6 +41,10 @@ impl Inner {
debug!("`Window::set_transparent` is ignored on iOS")
}
pub fn set_blur(&self, _blur: bool) {
debug!("`Window::set_blur` is ignored on iOS")
}
pub fn set_visible(&self, visible: bool) {
self.window.setHidden(!visible)
}
@@ -58,90 +55,81 @@ impl Inner {
}
pub fn request_redraw(&self) {
unsafe {
if self.gl_or_metal_backed {
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window.clone());
} else {
self.view.setNeedsDisplay();
}
if self.gl_or_metal_backed {
let mtm = MainThreadMarker::new().unwrap();
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
} else {
self.view.setNeedsDisplay();
}
}
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe {
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
unsafe {
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn set_outer_position(&self, physical_position: Position) {
unsafe {
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
}
let scale_factor = self.scale_factor();
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
self.window.setBounds(bounds);
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe {
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(scale_factor)
}
let scale_factor = self.scale_factor();
let safe_area = self.safe_area_screen_space();
let size = LogicalSize {
width: safe_area.size.width as f64,
height: safe_area.size.height as f64,
};
size.to_physical(scale_factor)
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
unsafe {
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(scale_factor)
}
let scale_factor = self.scale_factor();
let screen_frame = self.screen_frame();
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
size.to_physical(scale_factor)
}
pub fn set_inner_size(&self, _size: Size) {
warn!("not clear what `Window::set_inner_size` means on iOS");
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
@@ -209,6 +197,9 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
@@ -232,14 +223,17 @@ impl Inner {
}
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen();
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
uiscreen.clone()
}
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen(mtm).clone()
}
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
@@ -262,22 +256,21 @@ impl Inner {
}
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
unsafe {
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
let mtm = MainThreadMarker::new().unwrap();
let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen(mtm);
let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(Some(monitor)))
} else {
None
}
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x
&& screen_space_bounds.origin.y == screen_bounds.origin.y
&& screen_space_bounds.size.width == screen_bounds.size.width
&& screen_space_bounds.size.height == screen_bounds.size.height
{
Some(Fullscreen::Borderless(Some(monitor)))
} else {
None
}
}
@@ -338,16 +331,47 @@ impl Inner {
self.window.id()
}
pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty();
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::UiKitHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
RawWindowHandle::UiKit(window_handle)
rwh_04::RawWindowHandle::UiKit(window_handle)
}
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
#[cfg(feature = "rwh_05")]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
rwh_05::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_05")]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
});
window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
Ok(rwh_06::RawWindowHandle::UiKit(window_handle))
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
}
pub fn theme(&self) -> Option<Theme> {
@@ -355,6 +379,8 @@ impl Inner {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
pub fn has_focus(&self) -> bool {
self.window.isKeyWindow()
}
@@ -375,41 +401,16 @@ impl Inner {
}
pub struct Window {
pub inner: Inner,
}
impl Drop for Window {
fn drop(&mut self) {
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
}
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Deref for Window {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for Window {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
&mut self.inner
}
inner: MainThreadBound<Inner>,
}
impl Window {
pub(crate) fn new<T>(
_event_loop: &EventLoopWindowTarget<T>,
event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap();
let mtm = event_loop.mtm;
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
@@ -421,10 +422,10 @@ impl Window {
// TODO: transparency, visible
let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(None)) | None => &main_screen,
};
@@ -464,7 +465,7 @@ impl Window {
&view_controller,
);
unsafe { app_state::set_key_window(&window) };
app_state::set_key_window(mtm, &window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
@@ -476,50 +477,51 @@ impl Window {
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window: window.clone(),
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
let inner = Inner {
window,
view_controller,
view,
gl_or_metal_backed,
};
Ok(Window {
inner: Inner {
window,
view_controller,
view,
gl_or_metal_backed,
},
inner: MainThreadBound::new(inner, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Inner) -> R + Send) -> R {
self.inner.get_on_main(|inner, _mtm| f(inner))
}
}
// WindowExtIOS
impl Inner {
pub fn ui_window(&self) -> *mut c_void {
Id::as_ptr(&self.window) as *mut c_void
}
pub fn ui_view_controller(&self) -> *mut c_void {
Id::as_ptr(&self.view_controller) as *mut c_void
}
pub fn ui_view(&self) -> *mut c_void {
Id::as_ptr(&self.view) as *mut c_void
}
pub fn set_scale_factor(&self, scale_factor: f64) {
assert!(
dpi::validate_scale_factor(scale_factor),
@@ -538,42 +540,42 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller
.setPrefersHomeIndicatorAutoHidden(hidden);
.set_prefers_home_indicator_auto_hidden(hidden);
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
self.view_controller
.setPreferredScreenEdgesDeferringSystemGestures(edges);
.set_preferred_screen_edges_deferring_system_gestures(edges.into());
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.setPrefersStatusBarHidden(hidden);
self.view_controller.set_prefers_status_bar_hidden(hidden);
}
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.view_controller
.set_preferred_status_bar_style(status_bar_style.into());
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame(&self) -> CGRect {
fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds())
}
// requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_toCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_fromCoordinateSpace(rect, &screen_space)
}
// requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect {
fn safe_area_screen_space(&self) -> CGRect {
let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area {
let safe_area = self.window.safeAreaInsets();
@@ -591,7 +593,7 @@ impl Inner {
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
);
app.statusBarFrame()
@@ -648,8 +650,8 @@ impl From<u64> for WindowId {
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl From<&Object> for WindowId {
fn from(window: &Object) -> WindowId {
impl From<&AnyObject> for WindowId {
fn from(window: &AnyObject) -> WindowId {
WindowId {
window: window as *const _ as _,
}
@@ -662,5 +664,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_status_bar_style: StatusBarStyle,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}

View File

@@ -1,18 +1,18 @@
//! Convert XKB keys to Winit keys.
use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses.
pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode {
pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey {
scancode_to_keycode(keycode.saturating_sub(8))
}
/// Map the linux scancode to Keycode.
///
/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode.
pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey {
// The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as
// libxkbcommon's documentation seems to suggest that the keycode values we're interested in
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
@@ -21,8 +21,8 @@ pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
// Some of the keycodes are likely superfluous for our purposes, and some are ones which are
// difficult to test the correctness of, or discover the purpose of. Because of this, they've
// either been commented out here, or not included at all.
match scancode {
0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)),
PhysicalKey::Code(match scancode {
0 => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(0)),
1 => KeyCode::Escape,
2 => KeyCode::Digit1,
3 => KeyCode::Digit2,
@@ -256,7 +256,7 @@ pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
// 237 => KeyCode::BLUETOOTH,
// 238 => KeyCode::WLAN,
// 239 => KeyCode::UWB,
240 => KeyCode::Unidentified(NativeKeyCode::Unidentified),
240 => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
// 241 => KeyCode::VIDEO_NEXT,
// 242 => KeyCode::VIDEO_PREV,
// 243 => KeyCode::BRIGHTNESS_CYCLE,
@@ -265,14 +265,23 @@ pub fn scancode_to_keycode(scancode: u32) -> KeyCode {
// 246 => KeyCode::WWAN,
// 247 => KeyCode::RFKILL,
// 248 => KeyCode::KEY_MICMUTE,
_ => KeyCode::Unidentified(NativeKeyCode::Xkb(scancode)),
}
_ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)),
})
}
pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
match keycode {
KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240),
KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw),
pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
let code = match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(code) => {
return match code {
NativeKeyCode::Unidentified => Some(240),
NativeKeyCode::Xkb(raw) => Some(raw),
_ => None,
};
}
};
match code {
KeyCode::Escape => Some(1),
KeyCode::Digit1 => Some(2),
KeyCode::Digit2 => Some(3),
@@ -415,213 +424,213 @@ pub fn keycode_to_scancode(keycode: KeyCode) -> Option<u32> {
pub fn keysym_to_key(keysym: u32) -> Key {
use xkbcommon_dl::keysyms;
match keysym {
Key::Named(match keysym {
// TTY function keys
keysyms::BackSpace => Key::Backspace,
keysyms::Tab => Key::Tab,
// keysyms::Linefeed => Key::Linefeed,
keysyms::Clear => Key::Clear,
keysyms::Return => Key::Enter,
keysyms::Pause => Key::Pause,
keysyms::Scroll_Lock => Key::ScrollLock,
keysyms::Sys_Req => Key::PrintScreen,
keysyms::Escape => Key::Escape,
keysyms::Delete => Key::Delete,
keysyms::BackSpace => NamedKey::Backspace,
keysyms::Tab => NamedKey::Tab,
// keysyms::Linefeed => NamedKey::Linefeed,
keysyms::Clear => NamedKey::Clear,
keysyms::Return => NamedKey::Enter,
keysyms::Pause => NamedKey::Pause,
keysyms::Scroll_Lock => NamedKey::ScrollLock,
keysyms::Sys_Req => NamedKey::PrintScreen,
keysyms::Escape => NamedKey::Escape,
keysyms::Delete => NamedKey::Delete,
// IME keys
keysyms::Multi_key => Key::Compose,
keysyms::Codeinput => Key::CodeInput,
keysyms::SingleCandidate => Key::SingleCandidate,
keysyms::MultipleCandidate => Key::AllCandidates,
keysyms::PreviousCandidate => Key::PreviousCandidate,
keysyms::Multi_key => NamedKey::Compose,
keysyms::Codeinput => NamedKey::CodeInput,
keysyms::SingleCandidate => NamedKey::SingleCandidate,
keysyms::MultipleCandidate => NamedKey::AllCandidates,
keysyms::PreviousCandidate => NamedKey::PreviousCandidate,
// Japanese keys
keysyms::Kanji => Key::KanjiMode,
keysyms::Muhenkan => Key::NonConvert,
keysyms::Henkan_Mode => Key::Convert,
keysyms::Romaji => Key::Romaji,
keysyms::Hiragana => Key::Hiragana,
keysyms::Hiragana_Katakana => Key::HiraganaKatakana,
keysyms::Zenkaku => Key::Zenkaku,
keysyms::Hankaku => Key::Hankaku,
keysyms::Zenkaku_Hankaku => Key::ZenkakuHankaku,
// keysyms::Touroku => Key::Touroku,
// keysyms::Massyo => Key::Massyo,
keysyms::Kana_Lock => Key::KanaMode,
keysyms::Kana_Shift => Key::KanaMode,
keysyms::Eisu_Shift => Key::Alphanumeric,
keysyms::Eisu_toggle => Key::Alphanumeric,
keysyms::Kanji => NamedKey::KanjiMode,
keysyms::Muhenkan => NamedKey::NonConvert,
keysyms::Henkan_Mode => NamedKey::Convert,
keysyms::Romaji => NamedKey::Romaji,
keysyms::Hiragana => NamedKey::Hiragana,
keysyms::Hiragana_Katakana => NamedKey::HiraganaKatakana,
keysyms::Zenkaku => NamedKey::Zenkaku,
keysyms::Hankaku => NamedKey::Hankaku,
keysyms::Zenkaku_Hankaku => NamedKey::ZenkakuHankaku,
// keysyms::Touroku => NamedKey::Touroku,
// keysyms::Massyo => NamedKey::Massyo,
keysyms::Kana_Lock => NamedKey::KanaMode,
keysyms::Kana_Shift => NamedKey::KanaMode,
keysyms::Eisu_Shift => NamedKey::Alphanumeric,
keysyms::Eisu_toggle => NamedKey::Alphanumeric,
// NOTE: The next three items are aliases for values we've already mapped.
// keysyms::Kanji_Bangou => Key::CodeInput,
// keysyms::Zen_Koho => Key::AllCandidates,
// keysyms::Mae_Koho => Key::PreviousCandidate,
// keysyms::Kanji_Bangou => NamedKey::CodeInput,
// keysyms::Zen_Koho => NamedKey::AllCandidates,
// keysyms::Mae_Koho => NamedKey::PreviousCandidate,
// Cursor control & motion
keysyms::Home => Key::Home,
keysyms::Left => Key::ArrowLeft,
keysyms::Up => Key::ArrowUp,
keysyms::Right => Key::ArrowRight,
keysyms::Down => Key::ArrowDown,
// keysyms::Prior => Key::PageUp,
keysyms::Page_Up => Key::PageUp,
// keysyms::Next => Key::PageDown,
keysyms::Page_Down => Key::PageDown,
keysyms::End => Key::End,
// keysyms::Begin => Key::Begin,
keysyms::Home => NamedKey::Home,
keysyms::Left => NamedKey::ArrowLeft,
keysyms::Up => NamedKey::ArrowUp,
keysyms::Right => NamedKey::ArrowRight,
keysyms::Down => NamedKey::ArrowDown,
// keysyms::Prior => NamedKey::PageUp,
keysyms::Page_Up => NamedKey::PageUp,
// keysyms::Next => NamedKey::PageDown,
keysyms::Page_Down => NamedKey::PageDown,
keysyms::End => NamedKey::End,
// keysyms::Begin => NamedKey::Begin,
// Misc. functions
keysyms::Select => Key::Select,
keysyms::Print => Key::PrintScreen,
keysyms::Execute => Key::Execute,
keysyms::Insert => Key::Insert,
keysyms::Undo => Key::Undo,
keysyms::Redo => Key::Redo,
keysyms::Menu => Key::ContextMenu,
keysyms::Find => Key::Find,
keysyms::Cancel => Key::Cancel,
keysyms::Help => Key::Help,
keysyms::Break => Key::Pause,
keysyms::Mode_switch => Key::ModeChange,
// keysyms::script_switch => Key::ModeChange,
keysyms::Num_Lock => Key::NumLock,
keysyms::Select => NamedKey::Select,
keysyms::Print => NamedKey::PrintScreen,
keysyms::Execute => NamedKey::Execute,
keysyms::Insert => NamedKey::Insert,
keysyms::Undo => NamedKey::Undo,
keysyms::Redo => NamedKey::Redo,
keysyms::Menu => NamedKey::ContextMenu,
keysyms::Find => NamedKey::Find,
keysyms::Cancel => NamedKey::Cancel,
keysyms::Help => NamedKey::Help,
keysyms::Break => NamedKey::Pause,
keysyms::Mode_switch => NamedKey::ModeChange,
// keysyms::script_switch => NamedKey::ModeChange,
keysyms::Num_Lock => NamedKey::NumLock,
// Keypad keys
// keysyms::KP_Space => Key::Character(" "),
keysyms::KP_Tab => Key::Tab,
keysyms::KP_Enter => Key::Enter,
keysyms::KP_F1 => Key::F1,
keysyms::KP_F2 => Key::F2,
keysyms::KP_F3 => Key::F3,
keysyms::KP_F4 => Key::F4,
keysyms::KP_Home => Key::Home,
keysyms::KP_Left => Key::ArrowLeft,
keysyms::KP_Up => Key::ArrowLeft,
keysyms::KP_Right => Key::ArrowRight,
keysyms::KP_Down => Key::ArrowDown,
// keysyms::KP_Prior => Key::PageUp,
keysyms::KP_Page_Up => Key::PageUp,
// keysyms::KP_Next => Key::PageDown,
keysyms::KP_Page_Down => Key::PageDown,
keysyms::KP_End => Key::End,
// keysyms::KP_Space => return Key::Character(" "),
keysyms::KP_Tab => NamedKey::Tab,
keysyms::KP_Enter => NamedKey::Enter,
keysyms::KP_F1 => NamedKey::F1,
keysyms::KP_F2 => NamedKey::F2,
keysyms::KP_F3 => NamedKey::F3,
keysyms::KP_F4 => NamedKey::F4,
keysyms::KP_Home => NamedKey::Home,
keysyms::KP_Left => NamedKey::ArrowLeft,
keysyms::KP_Up => NamedKey::ArrowLeft,
keysyms::KP_Right => NamedKey::ArrowRight,
keysyms::KP_Down => NamedKey::ArrowDown,
// keysyms::KP_Prior => NamedKey::PageUp,
keysyms::KP_Page_Up => NamedKey::PageUp,
// keysyms::KP_Next => NamedKey::PageDown,
keysyms::KP_Page_Down => NamedKey::PageDown,
keysyms::KP_End => NamedKey::End,
// This is the key labeled "5" on the numpad when NumLock is off.
// keysyms::KP_Begin => Key::Begin,
keysyms::KP_Insert => Key::Insert,
keysyms::KP_Delete => Key::Delete,
// keysyms::KP_Equal => Key::Equal,
// keysyms::KP_Multiply => Key::Multiply,
// keysyms::KP_Add => Key::Add,
// keysyms::KP_Separator => Key::Separator,
// keysyms::KP_Subtract => Key::Subtract,
// keysyms::KP_Decimal => Key::Decimal,
// keysyms::KP_Divide => Key::Divide,
// keysyms::KP_Begin => NamedKey::Begin,
keysyms::KP_Insert => NamedKey::Insert,
keysyms::KP_Delete => NamedKey::Delete,
// keysyms::KP_Equal => NamedKey::Equal,
// keysyms::KP_Multiply => NamedKey::Multiply,
// keysyms::KP_Add => NamedKey::Add,
// keysyms::KP_Separator => NamedKey::Separator,
// keysyms::KP_Subtract => NamedKey::Subtract,
// keysyms::KP_Decimal => NamedKey::Decimal,
// keysyms::KP_Divide => NamedKey::Divide,
// keysyms::KP_0 => Key::Character("0"),
// keysyms::KP_1 => Key::Character("1"),
// keysyms::KP_2 => Key::Character("2"),
// keysyms::KP_3 => Key::Character("3"),
// keysyms::KP_4 => Key::Character("4"),
// keysyms::KP_5 => Key::Character("5"),
// keysyms::KP_6 => Key::Character("6"),
// keysyms::KP_7 => Key::Character("7"),
// keysyms::KP_8 => Key::Character("8"),
// keysyms::KP_9 => Key::Character("9"),
// keysyms::KP_0 => return Key::Character("0"),
// keysyms::KP_1 => return Key::Character("1"),
// keysyms::KP_2 => return Key::Character("2"),
// keysyms::KP_3 => return Key::Character("3"),
// keysyms::KP_4 => return Key::Character("4"),
// keysyms::KP_5 => return Key::Character("5"),
// keysyms::KP_6 => return Key::Character("6"),
// keysyms::KP_7 => return Key::Character("7"),
// keysyms::KP_8 => return Key::Character("8"),
// keysyms::KP_9 => return Key::Character("9"),
// Function keys
keysyms::F1 => Key::F1,
keysyms::F2 => Key::F2,
keysyms::F3 => Key::F3,
keysyms::F4 => Key::F4,
keysyms::F5 => Key::F5,
keysyms::F6 => Key::F6,
keysyms::F7 => Key::F7,
keysyms::F8 => Key::F8,
keysyms::F9 => Key::F9,
keysyms::F10 => Key::F10,
keysyms::F11 => Key::F11,
keysyms::F12 => Key::F12,
keysyms::F13 => Key::F13,
keysyms::F14 => Key::F14,
keysyms::F15 => Key::F15,
keysyms::F16 => Key::F16,
keysyms::F17 => Key::F17,
keysyms::F18 => Key::F18,
keysyms::F19 => Key::F19,
keysyms::F20 => Key::F20,
keysyms::F21 => Key::F21,
keysyms::F22 => Key::F22,
keysyms::F23 => Key::F23,
keysyms::F24 => Key::F24,
keysyms::F25 => Key::F25,
keysyms::F26 => Key::F26,
keysyms::F27 => Key::F27,
keysyms::F28 => Key::F28,
keysyms::F29 => Key::F29,
keysyms::F30 => Key::F30,
keysyms::F31 => Key::F31,
keysyms::F32 => Key::F32,
keysyms::F33 => Key::F33,
keysyms::F34 => Key::F34,
keysyms::F35 => Key::F35,
keysyms::F1 => NamedKey::F1,
keysyms::F2 => NamedKey::F2,
keysyms::F3 => NamedKey::F3,
keysyms::F4 => NamedKey::F4,
keysyms::F5 => NamedKey::F5,
keysyms::F6 => NamedKey::F6,
keysyms::F7 => NamedKey::F7,
keysyms::F8 => NamedKey::F8,
keysyms::F9 => NamedKey::F9,
keysyms::F10 => NamedKey::F10,
keysyms::F11 => NamedKey::F11,
keysyms::F12 => NamedKey::F12,
keysyms::F13 => NamedKey::F13,
keysyms::F14 => NamedKey::F14,
keysyms::F15 => NamedKey::F15,
keysyms::F16 => NamedKey::F16,
keysyms::F17 => NamedKey::F17,
keysyms::F18 => NamedKey::F18,
keysyms::F19 => NamedKey::F19,
keysyms::F20 => NamedKey::F20,
keysyms::F21 => NamedKey::F21,
keysyms::F22 => NamedKey::F22,
keysyms::F23 => NamedKey::F23,
keysyms::F24 => NamedKey::F24,
keysyms::F25 => NamedKey::F25,
keysyms::F26 => NamedKey::F26,
keysyms::F27 => NamedKey::F27,
keysyms::F28 => NamedKey::F28,
keysyms::F29 => NamedKey::F29,
keysyms::F30 => NamedKey::F30,
keysyms::F31 => NamedKey::F31,
keysyms::F32 => NamedKey::F32,
keysyms::F33 => NamedKey::F33,
keysyms::F34 => NamedKey::F34,
keysyms::F35 => NamedKey::F35,
// Modifiers
keysyms::Shift_L => Key::Shift,
keysyms::Shift_R => Key::Shift,
keysyms::Control_L => Key::Control,
keysyms::Control_R => Key::Control,
keysyms::Caps_Lock => Key::CapsLock,
// keysyms::Shift_Lock => Key::ShiftLock,
keysyms::Shift_L => NamedKey::Shift,
keysyms::Shift_R => NamedKey::Shift,
keysyms::Control_L => NamedKey::Control,
keysyms::Control_R => NamedKey::Control,
keysyms::Caps_Lock => NamedKey::CapsLock,
// keysyms::Shift_Lock => NamedKey::ShiftLock,
// keysyms::Meta_L => Key::Meta,
// keysyms::Meta_R => Key::Meta,
keysyms::Alt_L => Key::Alt,
keysyms::Alt_R => Key::Alt,
keysyms::Super_L => Key::Super,
keysyms::Super_R => Key::Super,
keysyms::Hyper_L => Key::Hyper,
keysyms::Hyper_R => Key::Hyper,
// keysyms::Meta_L => NamedKey::Meta,
// keysyms::Meta_R => NamedKey::Meta,
keysyms::Alt_L => NamedKey::Alt,
keysyms::Alt_R => NamedKey::Alt,
keysyms::Super_L => NamedKey::Super,
keysyms::Super_R => NamedKey::Super,
keysyms::Hyper_L => NamedKey::Hyper,
keysyms::Hyper_R => NamedKey::Hyper,
// XKB function and modifier keys
// keysyms::ISO_Lock => Key::IsoLock,
// keysyms::ISO_Level2_Latch => Key::IsoLevel2Latch,
keysyms::ISO_Level3_Shift => Key::AltGraph,
keysyms::ISO_Level3_Latch => Key::AltGraph,
keysyms::ISO_Level3_Lock => Key::AltGraph,
// keysyms::ISO_Level5_Shift => Key::IsoLevel5Shift,
// keysyms::ISO_Level5_Latch => Key::IsoLevel5Latch,
// keysyms::ISO_Level5_Lock => Key::IsoLevel5Lock,
// keysyms::ISO_Group_Shift => Key::IsoGroupShift,
// keysyms::ISO_Group_Latch => Key::IsoGroupLatch,
// keysyms::ISO_Group_Lock => Key::IsoGroupLock,
keysyms::ISO_Next_Group => Key::GroupNext,
// keysyms::ISO_Next_Group_Lock => Key::GroupNextLock,
keysyms::ISO_Prev_Group => Key::GroupPrevious,
// keysyms::ISO_Prev_Group_Lock => Key::GroupPreviousLock,
keysyms::ISO_First_Group => Key::GroupFirst,
// keysyms::ISO_First_Group_Lock => Key::GroupFirstLock,
keysyms::ISO_Last_Group => Key::GroupLast,
// keysyms::ISO_Last_Group_Lock => Key::GroupLastLock,
// keysyms::ISO_Lock => NamedKey::IsoLock,
// keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch,
keysyms::ISO_Level3_Shift => NamedKey::AltGraph,
keysyms::ISO_Level3_Latch => NamedKey::AltGraph,
keysyms::ISO_Level3_Lock => NamedKey::AltGraph,
// keysyms::ISO_Level5_Shift => NamedKey::IsoLevel5Shift,
// keysyms::ISO_Level5_Latch => NamedKey::IsoLevel5Latch,
// keysyms::ISO_Level5_Lock => NamedKey::IsoLevel5Lock,
// keysyms::ISO_Group_Shift => NamedKey::IsoGroupShift,
// keysyms::ISO_Group_Latch => NamedKey::IsoGroupLatch,
// keysyms::ISO_Group_Lock => NamedKey::IsoGroupLock,
keysyms::ISO_Next_Group => NamedKey::GroupNext,
// keysyms::ISO_Next_Group_Lock => NamedKey::GroupNextLock,
keysyms::ISO_Prev_Group => NamedKey::GroupPrevious,
// keysyms::ISO_Prev_Group_Lock => NamedKey::GroupPreviousLock,
keysyms::ISO_First_Group => NamedKey::GroupFirst,
// keysyms::ISO_First_Group_Lock => NamedKey::GroupFirstLock,
keysyms::ISO_Last_Group => NamedKey::GroupLast,
// keysyms::ISO_Last_Group_Lock => NamedKey::GroupLastLock,
//
keysyms::ISO_Left_Tab => Key::Tab,
// keysyms::ISO_Move_Line_Up => Key::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => Key::IsoMoveLineDown,
// keysyms::ISO_Partial_Line_Up => Key::IsoPartialLineUp,
// keysyms::ISO_Partial_Line_Down => Key::IsoPartialLineDown,
// keysyms::ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft,
// keysyms::ISO_Partial_Space_Right => Key::IsoPartialSpaceRight,
// keysyms::ISO_Set_Margin_Left => Key::IsoSetMarginLeft,
// keysyms::ISO_Set_Margin_Right => Key::IsoSetMarginRight,
// keysyms::ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => Key::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => Key::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => Key::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => Key::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => Key::IsoFastCursorDown,
// keysyms::ISO_Continuous_Underline => Key::IsoContinuousUnderline,
// keysyms::ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline,
// keysyms::ISO_Emphasize => Key::IsoEmphasize,
// keysyms::ISO_Center_Object => Key::IsoCenterObject,
keysyms::ISO_Enter => Key::Enter,
keysyms::ISO_Left_Tab => NamedKey::Tab,
// keysyms::ISO_Move_Line_Up => NamedKey::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => NamedKey::IsoMoveLineDown,
// keysyms::ISO_Partial_Line_Up => NamedKey::IsoPartialLineUp,
// keysyms::ISO_Partial_Line_Down => NamedKey::IsoPartialLineDown,
// keysyms::ISO_Partial_Space_Left => NamedKey::IsoPartialSpaceLeft,
// keysyms::ISO_Partial_Space_Right => NamedKey::IsoPartialSpaceRight,
// keysyms::ISO_Set_Margin_Left => NamedKey::IsoSetMarginLeft,
// keysyms::ISO_Set_Margin_Right => NamedKey::IsoSetMarginRight,
// keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft,
// keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight,
// keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins,
// keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft,
// keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown,
// keysyms::ISO_Continuous_Underline => NamedKey::IsoContinuousUnderline,
// keysyms::ISO_Discontinuous_Underline => NamedKey::IsoDiscontinuousUnderline,
// keysyms::ISO_Emphasize => NamedKey::IsoEmphasize,
// keysyms::ISO_Center_Object => NamedKey::IsoCenterObject,
keysyms::ISO_Enter => NamedKey::Enter,
// dead_grave..dead_currency
@@ -642,194 +651,194 @@ pub fn keysym_to_key(keysym: u32) -> Key {
// ch..C_H
// 3270 terminal keys
// keysyms::3270_Duplicate => Key::Duplicate,
// keysyms::3270_FieldMark => Key::FieldMark,
// keysyms::3270_Right2 => Key::Right2,
// keysyms::3270_Left2 => Key::Left2,
// keysyms::3270_BackTab => Key::BackTab,
keysyms::_3270_EraseEOF => Key::EraseEof,
// keysyms::3270_EraseInput => Key::EraseInput,
// keysyms::3270_Reset => Key::Reset,
// keysyms::3270_Quit => Key::Quit,
// keysyms::3270_PA1 => Key::Pa1,
// keysyms::3270_PA2 => Key::Pa2,
// keysyms::3270_PA3 => Key::Pa3,
// keysyms::3270_Test => Key::Test,
keysyms::_3270_Attn => Key::Attn,
// keysyms::3270_CursorBlink => Key::CursorBlink,
// keysyms::3270_AltCursor => Key::AltCursor,
// keysyms::3270_KeyClick => Key::KeyClick,
// keysyms::3270_Jump => Key::Jump,
// keysyms::3270_Ident => Key::Ident,
// keysyms::3270_Rule => Key::Rule,
// keysyms::3270_Copy => Key::Copy,
keysyms::_3270_Play => Key::Play,
// keysyms::3270_Setup => Key::Setup,
// keysyms::3270_Record => Key::Record,
// keysyms::3270_ChangeScreen => Key::ChangeScreen,
// keysyms::3270_DeleteWord => Key::DeleteWord,
keysyms::_3270_ExSelect => Key::ExSel,
keysyms::_3270_CursorSelect => Key::CrSel,
keysyms::_3270_PrintScreen => Key::PrintScreen,
keysyms::_3270_Enter => Key::Enter,
// keysyms::3270_Duplicate => NamedKey::Duplicate,
// keysyms::3270_FieldMark => NamedKey::FieldMark,
// keysyms::3270_Right2 => NamedKey::Right2,
// keysyms::3270_Left2 => NamedKey::Left2,
// keysyms::3270_BackTab => NamedKey::BackTab,
keysyms::_3270_EraseEOF => NamedKey::EraseEof,
// keysyms::3270_EraseInput => NamedKey::EraseInput,
// keysyms::3270_Reset => NamedKey::Reset,
// keysyms::3270_Quit => NamedKey::Quit,
// keysyms::3270_PA1 => NamedKey::Pa1,
// keysyms::3270_PA2 => NamedKey::Pa2,
// keysyms::3270_PA3 => NamedKey::Pa3,
// keysyms::3270_Test => NamedKey::Test,
keysyms::_3270_Attn => NamedKey::Attn,
// keysyms::3270_CursorBlink => NamedKey::CursorBlink,
// keysyms::3270_AltCursor => NamedKey::AltCursor,
// keysyms::3270_KeyClick => NamedKey::KeyClick,
// keysyms::3270_Jump => NamedKey::Jump,
// keysyms::3270_Ident => NamedKey::Ident,
// keysyms::3270_Rule => NamedKey::Rule,
// keysyms::3270_Copy => NamedKey::Copy,
keysyms::_3270_Play => NamedKey::Play,
// keysyms::3270_Setup => NamedKey::Setup,
// keysyms::3270_Record => NamedKey::Record,
// keysyms::3270_ChangeScreen => NamedKey::ChangeScreen,
// keysyms::3270_DeleteWord => NamedKey::DeleteWord,
keysyms::_3270_ExSelect => NamedKey::ExSel,
keysyms::_3270_CursorSelect => NamedKey::CrSel,
keysyms::_3270_PrintScreen => NamedKey::PrintScreen,
keysyms::_3270_Enter => NamedKey::Enter,
keysyms::space => Key::Space,
keysyms::space => NamedKey::Space,
// exclam..Sinh_kunddaliya
// XFree86
// keysyms::XF86_ModeLock => Key::ModeLock,
// keysyms::XF86_ModeLock => NamedKey::ModeLock,
// XFree86 - Backlight controls
keysyms::XF86_MonBrightnessUp => Key::BrightnessUp,
keysyms::XF86_MonBrightnessDown => Key::BrightnessDown,
// keysyms::XF86_KbdLightOnOff => Key::LightOnOff,
// keysyms::XF86_KbdBrightnessUp => Key::KeyboardBrightnessUp,
// keysyms::XF86_KbdBrightnessDown => Key::KeyboardBrightnessDown,
keysyms::XF86_MonBrightnessUp => NamedKey::BrightnessUp,
keysyms::XF86_MonBrightnessDown => NamedKey::BrightnessDown,
// keysyms::XF86_KbdLightOnOff => NamedKey::LightOnOff,
// keysyms::XF86_KbdBrightnessUp => NamedKey::KeyboardBrightnessUp,
// keysyms::XF86_KbdBrightnessDown => NamedKey::KeyboardBrightnessDown,
// XFree86 - "Internet"
keysyms::XF86_Standby => Key::Standby,
keysyms::XF86_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::XF86_AudioRaiseVolume => Key::AudioVolumeUp,
keysyms::XF86_AudioPlay => Key::MediaPlay,
keysyms::XF86_AudioStop => Key::MediaStop,
keysyms::XF86_AudioPrev => Key::MediaTrackPrevious,
keysyms::XF86_AudioNext => Key::MediaTrackNext,
keysyms::XF86_HomePage => Key::BrowserHome,
keysyms::XF86_Mail => Key::LaunchMail,
// keysyms::XF86_Start => Key::Start,
keysyms::XF86_Search => Key::BrowserSearch,
keysyms::XF86_AudioRecord => Key::MediaRecord,
keysyms::XF86_Standby => NamedKey::Standby,
keysyms::XF86_AudioLowerVolume => NamedKey::AudioVolumeDown,
keysyms::XF86_AudioRaiseVolume => NamedKey::AudioVolumeUp,
keysyms::XF86_AudioPlay => NamedKey::MediaPlay,
keysyms::XF86_AudioStop => NamedKey::MediaStop,
keysyms::XF86_AudioPrev => NamedKey::MediaTrackPrevious,
keysyms::XF86_AudioNext => NamedKey::MediaTrackNext,
keysyms::XF86_HomePage => NamedKey::BrowserHome,
keysyms::XF86_Mail => NamedKey::LaunchMail,
// keysyms::XF86_Start => NamedKey::Start,
keysyms::XF86_Search => NamedKey::BrowserSearch,
keysyms::XF86_AudioRecord => NamedKey::MediaRecord,
// XFree86 - PDA
keysyms::XF86_Calculator => Key::LaunchApplication2,
// keysyms::XF86_Memo => Key::Memo,
// keysyms::XF86_ToDoList => Key::ToDoList,
keysyms::XF86_Calendar => Key::LaunchCalendar,
keysyms::XF86_PowerDown => Key::Power,
// keysyms::XF86_ContrastAdjust => Key::AdjustContrast,
// keysyms::XF86_RockerUp => Key::RockerUp,
// keysyms::XF86_RockerDown => Key::RockerDown,
// keysyms::XF86_RockerEnter => Key::RockerEnter,
keysyms::XF86_Calculator => NamedKey::LaunchApplication2,
// keysyms::XF86_Memo => NamedKey::Memo,
// keysyms::XF86_ToDoList => NamedKey::ToDoList,
keysyms::XF86_Calendar => NamedKey::LaunchCalendar,
keysyms::XF86_PowerDown => NamedKey::Power,
// keysyms::XF86_ContrastAdjust => NamedKey::AdjustContrast,
// keysyms::XF86_RockerUp => NamedKey::RockerUp,
// keysyms::XF86_RockerDown => NamedKey::RockerDown,
// keysyms::XF86_RockerEnter => NamedKey::RockerEnter,
// XFree86 - More "Internet"
keysyms::XF86_Back => Key::BrowserBack,
keysyms::XF86_Forward => Key::BrowserForward,
// keysyms::XF86_Stop => Key::Stop,
keysyms::XF86_Refresh => Key::BrowserRefresh,
keysyms::XF86_PowerOff => Key::Power,
keysyms::XF86_WakeUp => Key::WakeUp,
keysyms::XF86_Eject => Key::Eject,
keysyms::XF86_ScreenSaver => Key::LaunchScreenSaver,
keysyms::XF86_WWW => Key::LaunchWebBrowser,
keysyms::XF86_Sleep => Key::Standby,
keysyms::XF86_Favorites => Key::BrowserFavorites,
keysyms::XF86_AudioPause => Key::MediaPause,
// keysyms::XF86_AudioMedia => Key::AudioMedia,
keysyms::XF86_MyComputer => Key::LaunchApplication1,
// keysyms::XF86_VendorHome => Key::VendorHome,
// keysyms::XF86_LightBulb => Key::LightBulb,
// keysyms::XF86_Shop => Key::BrowserShop,
// keysyms::XF86_History => Key::BrowserHistory,
// keysyms::XF86_OpenURL => Key::OpenUrl,
// keysyms::XF86_AddFavorite => Key::AddFavorite,
// keysyms::XF86_HotLinks => Key::HotLinks,
// keysyms::XF86_BrightnessAdjust => Key::BrightnessAdjust,
// keysyms::XF86_Finance => Key::BrowserFinance,
// keysyms::XF86_Community => Key::BrowserCommunity,
keysyms::XF86_AudioRewind => Key::MediaRewind,
keysyms::XF86_Back => NamedKey::BrowserBack,
keysyms::XF86_Forward => NamedKey::BrowserForward,
// keysyms::XF86_Stop => NamedKey::Stop,
keysyms::XF86_Refresh => NamedKey::BrowserRefresh,
keysyms::XF86_PowerOff => NamedKey::Power,
keysyms::XF86_WakeUp => NamedKey::WakeUp,
keysyms::XF86_Eject => NamedKey::Eject,
keysyms::XF86_ScreenSaver => NamedKey::LaunchScreenSaver,
keysyms::XF86_WWW => NamedKey::LaunchWebBrowser,
keysyms::XF86_Sleep => NamedKey::Standby,
keysyms::XF86_Favorites => NamedKey::BrowserFavorites,
keysyms::XF86_AudioPause => NamedKey::MediaPause,
// keysyms::XF86_AudioMedia => NamedKey::AudioMedia,
keysyms::XF86_MyComputer => NamedKey::LaunchApplication1,
// keysyms::XF86_VendorHome => NamedKey::VendorHome,
// keysyms::XF86_LightBulb => NamedKey::LightBulb,
// keysyms::XF86_Shop => NamedKey::BrowserShop,
// keysyms::XF86_History => NamedKey::BrowserHistory,
// keysyms::XF86_OpenURL => NamedKey::OpenUrl,
// keysyms::XF86_AddFavorite => NamedKey::AddFavorite,
// keysyms::XF86_HotLinks => NamedKey::HotLinks,
// keysyms::XF86_BrightnessAdjust => NamedKey::BrightnessAdjust,
// keysyms::XF86_Finance => NamedKey::BrowserFinance,
// keysyms::XF86_Community => NamedKey::BrowserCommunity,
keysyms::XF86_AudioRewind => NamedKey::MediaRewind,
// keysyms::XF86_BackForward => Key::???,
// XF86_Launch0..XF86_LaunchF
// XF86_ApplicationLeft..XF86_CD
keysyms::XF86_Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :)
keysyms::XF86_Calculater => NamedKey::LaunchApplication2, // Nice typo, libxkbcommon :)
// XF86_Clear
keysyms::XF86_Close => Key::Close,
keysyms::XF86_Copy => Key::Copy,
keysyms::XF86_Cut => Key::Cut,
keysyms::XF86_Close => NamedKey::Close,
keysyms::XF86_Copy => NamedKey::Copy,
keysyms::XF86_Cut => NamedKey::Cut,
// XF86_Display..XF86_Documents
keysyms::XF86_Excel => Key::LaunchSpreadsheet,
keysyms::XF86_Excel => NamedKey::LaunchSpreadsheet,
// XF86_Explorer..XF86iTouch
keysyms::XF86_LogOff => Key::LogOff,
keysyms::XF86_LogOff => NamedKey::LogOff,
// XF86_Market..XF86_MenuPB
keysyms::XF86_MySites => Key::BrowserFavorites,
keysyms::XF86_New => Key::New,
keysyms::XF86_MySites => NamedKey::BrowserFavorites,
keysyms::XF86_New => NamedKey::New,
// XF86_News..XF86_OfficeHome
keysyms::XF86_Open => Key::Open,
keysyms::XF86_Open => NamedKey::Open,
// XF86_Option
keysyms::XF86_Paste => Key::Paste,
keysyms::XF86_Phone => Key::LaunchPhone,
keysyms::XF86_Paste => NamedKey::Paste,
keysyms::XF86_Phone => NamedKey::LaunchPhone,
// XF86_Q
keysyms::XF86_Reply => Key::MailReply,
keysyms::XF86_Reload => Key::BrowserRefresh,
keysyms::XF86_Reply => NamedKey::MailReply,
keysyms::XF86_Reload => NamedKey::BrowserRefresh,
// XF86_RotateWindows..XF86_RotationKB
keysyms::XF86_Save => Key::Save,
keysyms::XF86_Save => NamedKey::Save,
// XF86_ScrollUp..XF86_ScrollClick
keysyms::XF86_Send => Key::MailSend,
keysyms::XF86_Spell => Key::SpellCheck,
keysyms::XF86_SplitScreen => Key::SplitScreenToggle,
keysyms::XF86_Send => NamedKey::MailSend,
keysyms::XF86_Spell => NamedKey::SpellCheck,
keysyms::XF86_SplitScreen => NamedKey::SplitScreenToggle,
// XF86_Support..XF86_User2KB
keysyms::XF86_Video => Key::LaunchMediaPlayer,
keysyms::XF86_Video => NamedKey::LaunchMediaPlayer,
// XF86_WheelButton
keysyms::XF86_Word => Key::LaunchWordProcessor,
keysyms::XF86_Word => NamedKey::LaunchWordProcessor,
// XF86_Xfer
keysyms::XF86_ZoomIn => Key::ZoomIn,
keysyms::XF86_ZoomOut => Key::ZoomOut,
keysyms::XF86_ZoomIn => NamedKey::ZoomIn,
keysyms::XF86_ZoomOut => NamedKey::ZoomOut,
// XF86_Away..XF86_Messenger
keysyms::XF86_WebCam => Key::LaunchWebCam,
keysyms::XF86_MailForward => Key::MailForward,
keysyms::XF86_WebCam => NamedKey::LaunchWebCam,
keysyms::XF86_MailForward => NamedKey::MailForward,
// XF86_Pictures
keysyms::XF86_Music => Key::LaunchMusicPlayer,
keysyms::XF86_Music => NamedKey::LaunchMusicPlayer,
// XF86_Battery..XF86_UWB
//
keysyms::XF86_AudioForward => Key::MediaFastForward,
keysyms::XF86_AudioForward => NamedKey::MediaFastForward,
// XF86_AudioRepeat
keysyms::XF86_AudioRandomPlay => Key::RandomToggle,
keysyms::XF86_Subtitle => Key::Subtitle,
keysyms::XF86_AudioCycleTrack => Key::MediaAudioTrack,
keysyms::XF86_AudioRandomPlay => NamedKey::RandomToggle,
keysyms::XF86_Subtitle => NamedKey::Subtitle,
keysyms::XF86_AudioCycleTrack => NamedKey::MediaAudioTrack,
// XF86_CycleAngle..XF86_Blue
//
keysyms::XF86_Suspend => Key::Standby,
keysyms::XF86_Hibernate => Key::Hibernate,
keysyms::XF86_Suspend => NamedKey::Standby,
keysyms::XF86_Hibernate => NamedKey::Hibernate,
// XF86_TouchpadToggle..XF86_TouchpadOff
//
keysyms::XF86_AudioMute => Key::AudioVolumeMute,
keysyms::XF86_AudioMute => NamedKey::AudioVolumeMute,
// XF86_Switch_VT_1..XF86_Switch_VT_12
// XF86_Ungrab..XF86_ClearGrab
keysyms::XF86_Next_VMode => Key::VideoModeNext,
// keysyms::XF86_Prev_VMode => Key::VideoModePrevious,
keysyms::XF86_Next_VMode => NamedKey::VideoModeNext,
// keysyms::XF86_Prev_VMode => NamedKey::VideoModePrevious,
// XF86_LogWindowTree..XF86_LogGrabInfo
// SunFA_Grave..SunFA_Cedilla
// keysyms::SunF36 => Key::F36 | Key::F11,
// keysyms::SunF37 => Key::F37 | Key::F12,
// keysyms::SunF36 => NamedKey::F36 | NamedKey::F11,
// keysyms::SunF37 => NamedKey::F37 | NamedKey::F12,
// keysyms::SunSys_Req => Key::PrintScreen,
// keysyms::SunSys_Req => NamedKey::PrintScreen,
// The next couple of xkb (until SunStop) are already handled.
// SunPrint_Screen..SunPageDown
// SunUndo..SunFront
keysyms::SUN_Copy => Key::Copy,
keysyms::SUN_Open => Key::Open,
keysyms::SUN_Paste => Key::Paste,
keysyms::SUN_Cut => Key::Cut,
keysyms::SUN_Copy => NamedKey::Copy,
keysyms::SUN_Open => NamedKey::Open,
keysyms::SUN_Paste => NamedKey::Paste,
keysyms::SUN_Cut => NamedKey::Cut,
// SunPowerSwitch
keysyms::SUN_AudioLowerVolume => Key::AudioVolumeDown,
keysyms::SUN_AudioMute => Key::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => Key::AudioVolumeUp,
keysyms::SUN_AudioLowerVolume => NamedKey::AudioVolumeDown,
keysyms::SUN_AudioMute => NamedKey::AudioVolumeMute,
keysyms::SUN_AudioRaiseVolume => NamedKey::AudioVolumeUp,
// SUN_VideoDegauss
keysyms::SUN_VideoLowerBrightness => Key::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => Key::BrightnessUp,
keysyms::SUN_VideoLowerBrightness => NamedKey::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => NamedKey::BrightnessUp,
// SunPowerSwitchShift
//
0 => Key::Unidentified(NativeKey::Unidentified),
_ => Key::Unidentified(NativeKey::Xkb(keysym)),
}
0 => return Key::Unidentified(NativeKey::Unidentified),
_ => return Key::Unidentified(NativeKey::Xkb(keysym)),
})
}
pub fn keysym_location(keysym: u32) -> KeyLocation {

View File

@@ -13,7 +13,7 @@ use xkbcommon_dl::{
XkbCommonCompose,
};
#[cfg(feature = "wayland")]
use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd};
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
@@ -22,7 +22,7 @@ use crate::platform_impl::common::keymap;
use crate::platform_impl::KeyEventExtra;
use crate::{
event::ElementState,
keyboard::{Key, KeyCode, KeyLocation},
keyboard::{Key, KeyLocation, PhysicalKey},
};
// TODO: Wire this up without using a static `AtomicBool`.
@@ -250,37 +250,43 @@ impl KbdState {
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let compose_table = (XKBCH.xkb_compose_table_new_from_locale)(
self.xkb_context,
locale.as_ptr(),
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
);
let compose_table = unsafe {
(XKBCH.xkb_compose_table_new_from_locale)(
self.xkb_context,
locale.as_ptr(),
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
)
};
if compose_table.is_null() {
// init of compose table failed, continue without compose
return;
}
let compose_state = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
let compose_state = unsafe {
(XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
if compose_state.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
return;
}
let compose_state_2 = (XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
let compose_state_2 = unsafe {
(XKBCH.xkb_compose_state_new)(
compose_table,
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
if compose_state_2.is_null() {
// init of compose state failed, continue without compose
(XKBCH.xkb_compose_table_unref)(compose_table);
(XKBCH.xkb_compose_state_unref)(compose_state);
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) };
return;
}
@@ -296,65 +302,73 @@ impl KbdState {
}
unsafe fn de_init(&mut self) {
(XKBH.xkb_state_unref)(self.xkb_state);
unsafe { (XKBH.xkb_state_unref)(self.xkb_state) };
self.xkb_state = ptr::null_mut();
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) };
self.xkb_keymap = ptr::null_mut();
}
#[cfg(feature = "x11")]
pub unsafe fn init_with_x11_keymap(&mut self) {
if !self.xkb_keymap.is_null() {
self.de_init();
unsafe { self.de_init() };
}
// TODO: Support keyboards other than the "virtual core keyboard device".
self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection);
let keymap = (XKBXH.xkb_x11_keymap_new_from_device)(
self.xkb_context,
self.xcb_connection,
self.core_keyboard_id,
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
self.core_keyboard_id =
unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) };
let keymap = unsafe {
(XKBXH.xkb_x11_keymap_new_from_device)(
self.xkb_context,
self.xcb_connection,
self.core_keyboard_id,
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
if keymap.is_null() {
panic!("Failed to get keymap from X11 server.");
}
let state = (XKBXH.xkb_x11_state_new_from_device)(
keymap,
self.xcb_connection,
self.core_keyboard_id,
);
self.post_init(state, keymap);
let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(
keymap,
self.xcb_connection,
self.core_keyboard_id,
)
};
unsafe { self.post_init(state, keymap) };
}
#[cfg(feature = "wayland")]
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
if !self.xkb_keymap.is_null() {
self.de_init();
unsafe { self.de_init() };
}
let map = MmapOptions::new()
.len(size)
.map_copy_read_only(&fd)
.unwrap();
let map = unsafe {
MmapOptions::new()
.len(size)
.map_copy_read_only(&fd)
.unwrap()
};
let keymap = (XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
let keymap = unsafe {
(XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
)
};
if keymap.is_null() {
panic!("Received invalid keymap from compositor.");
}
let state = (XKBH.xkb_state_new)(keymap);
self.post_init(state, keymap);
let state = unsafe { (XKBH.xkb_state_new)(keymap) };
unsafe { self.post_init(state, keymap) };
}
#[cfg(feature = "wayland")]
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
}
@@ -377,7 +391,7 @@ impl KbdState {
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = event.keycode();
let physical_key = event.physical_key();
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
@@ -484,8 +498,8 @@ impl<'a> KeyEventResults<'a> {
}
}
fn keycode(&mut self) -> KeyCode {
keymap::raw_keycode_to_keycode(self.keycode)
fn physical_key(&mut self) -> PhysicalKey {
keymap::raw_keycode_to_physicalkey(self.keycode)
}
pub fn key(&mut self) -> (Key, KeyLocation) {

View File

@@ -3,45 +3,45 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(wayland_platform)]
use std::error::Error;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::Arc;
use std::time::Duration;
use std::{collections::VecDeque, env, fmt};
#[cfg(x11_platform)]
use std::{
ffi::CStr,
mem::MaybeUninit,
os::raw::*,
sync::{Arc, Mutex},
};
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
#[cfg(x11_platform)]
use once_cell::sync::Lazy;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use smol_str::SmolStr;
#[cfg(x11_platform)]
pub use self::x11::XNotSupported;
#[cfg(x11_platform)]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, KeyEvent},
event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
event::KeyEvent,
event_loop::{
AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
EventLoopWindowTarget as RootELW,
},
icon::Icon,
keyboard::{Key, KeyCode},
platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode},
keyboard::{Key, PhysicalKey},
platform::{
modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus,
scancode::PhysicalKeyExtScancode,
},
window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
},
};
#[cfg(x11_platform)]
pub use x11::XNotSupported;
#[cfg(x11_platform)]
use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen;
pub(crate) use crate::platform_impl::Fullscreen;
pub mod common;
#[cfg(wayland_platform)]
@@ -49,15 +49,6 @@ pub mod wayland;
#[cfg(x11_platform)]
pub mod x11;
/// Environment variable specifying which backend should be used on unix platform.
///
/// Legal values are x11 and wayland. If this variable is set only the named backend
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
/// and if it fails will fallback on x11.
///
/// If this variable is set with any other value, winit will panic.
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(x11_platform)]
@@ -87,32 +78,38 @@ impl ApplicationName {
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>,
#[cfg(x11_platform)]
pub visual_infos: Option<XVisualInfo>,
#[cfg(x11_platform)]
pub x11: X11WindowBuilderAttributes,
}
#[derive(Clone)]
#[cfg(x11_platform)]
pub struct X11WindowBuilderAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
pub screen_id: Option<i32>,
#[cfg(x11_platform)]
pub base_size: Option<Size>,
#[cfg(x11_platform)]
pub override_redirect: bool,
#[cfg(x11_platform)]
pub x11_window_types: Vec<XWindowType>,
/// The parent window to embed this window into.
pub embed_window: Option<x11rb::protocol::xproto::Window>,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default() -> Self {
Self {
name: None,
activation_token: None,
#[cfg(x11_platform)]
visual_infos: None,
#[cfg(x11_platform)]
screen_id: None,
#[cfg(x11_platform)]
base_size: None,
#[cfg(x11_platform)]
override_redirect: false,
#[cfg(x11_platform)]
x11_window_types: vec![XWindowType::Normal],
x11: X11WindowBuilderAttributes {
visual_id: None,
screen_id: None,
base_size: None,
override_redirect: false,
x11_window_types: vec![XWindowType::Normal],
embed_window: None,
},
}
}
}
@@ -123,23 +120,21 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
#[derive(Debug, Clone)]
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XError(XError),
#[cfg(x11_platform)]
XMisc(&'static str),
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandMisc(&'static str),
WaylandError(Arc<wayland::WaylandError>),
}
impl fmt::Display for OsError {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XError(ref e) => _f.pad(&e.description),
#[cfg(x11_platform)]
OsError::XMisc(e) => _f.pad(e),
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandMisc(e) => _f.pad(e),
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
}
}
}
@@ -183,9 +178,9 @@ pub enum DeviceId {
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
}
}
@@ -288,7 +283,7 @@ impl VideoMode {
#[inline]
pub fn monitor(&self) -> MonitorHandle {
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
x11_or_wayland!(match self; VideoMode(m) => m.monitor(); as MonitorHandle)
}
}
@@ -311,14 +306,17 @@ impl Window {
}
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
}
#[inline]
pub fn id(&self) -> WindowId {
match self {
#[cfg(wayland_platform)]
Self::Wayland(window) => window.id(),
#[cfg(x11_platform)]
Self::X(window) => window.id(),
}
x11_or_wayland!(match self; Window(w) => w.id())
}
#[inline]
@@ -331,6 +329,11 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent));
}
#[inline]
pub fn set_blur(&self, blur: bool) {
x11_or_wayland!(match self; Window(w) => w.set_blur(blur));
}
#[inline]
pub fn set_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
@@ -367,8 +370,13 @@ impl Window {
}
#[inline]
pub fn set_inner_size(&self, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.request_inner_size(size))
}
#[inline]
pub(crate) fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.request_activation_token())
}
#[inline]
@@ -436,6 +444,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline]
pub fn show_window_menu(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.show_window_menu(position))
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
@@ -492,23 +505,13 @@ impl Window {
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_level(_level),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
pub fn set_window_level(&self, level: WindowLevel) {
x11_or_wayland!(match self; Window(w) => w.set_window_level(level))
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.set_window_icon(_window_icon),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
}
#[inline]
@@ -533,20 +536,10 @@ impl Window {
#[inline]
pub fn focus_window(&self) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.focus_window(),
#[cfg(wayland_platform)]
Window::Wayland(_) => (),
}
x11_or_wayland!(match self; Window(w) => w.focus_window())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
match self {
#[cfg(x11_platform)]
Window::X(ref w) => w.request_user_attention(request_type),
#[cfg(wayland_platform)]
Window::Wayland(ref w) => w.request_user_attention(request_type),
}
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
}
#[inline]
@@ -554,20 +547,14 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.request_redraw())
}
#[inline]
pub fn pre_present_notify(&self) {
x11_or_wayland!(match self; Window(w) => w.pre_present_notify())
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let current_monitor = MonitorHandle::X(window.current_monitor());
Some(current_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
Some(current_monitor)
}
}
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
}
#[inline]
@@ -590,25 +577,39 @@ impl Window {
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
let primary_monitor = MonitorHandle::X(window.primary_monitor());
Some(primary_monitor)
}
#[cfg(wayland_platform)]
Window::Wayland(ref window) => window.primary_monitor(),
}
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle(&self) -> RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle(&self) -> RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05())
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06())
}
#[inline]
@@ -621,6 +622,10 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.theme())
}
pub fn set_content_protected(&self, protected: bool) {
x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected))
}
#[inline]
pub fn has_focus(&self) -> bool {
x11_or_wayland!(match self; Window(window) => window.has_focus())
@@ -652,13 +657,13 @@ impl KeyEventExtModifierSupplement for KeyEvent {
}
}
impl KeyCodeExtScancode for KeyCode {
fn from_scancode(scancode: u32) -> KeyCode {
impl PhysicalKeyExtScancode for PhysicalKey {
fn from_scancode(scancode: u32) -> PhysicalKey {
common::keymap::scancode_to_keycode(scancode)
}
fn to_scancode(self) -> Option<u32> {
common::keymap::keycode_to_scancode(self)
common::keymap::physicalkey_to_scancode(self)
}
}
@@ -676,26 +681,31 @@ unsafe extern "C" fn x_error_callback(
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
);
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
unsafe {
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
)
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
let error = unsafe {
XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
}
};
// Don't log error.
@@ -730,7 +740,9 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
if !attributes.any_thread && !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
@@ -740,65 +752,44 @@ impl<T: 'static> EventLoop<T> {
);
}
#[cfg(x11_platform)]
if attributes.forced_backend == Some(Backend::X) {
// TODO: Propagate
return EventLoop::new_x11_any_thread().unwrap();
}
#[cfg(wayland_platform)]
if attributes.forced_backend == Some(Backend::Wayland) {
// TODO: Propagate
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
}
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
match env_var.as_str() {
"x11" => {
// TODO: propagate
#[cfg(x11_platform)]
return EventLoop::new_x11_any_thread()
.expect("Failed to initialize X11 backend");
#[cfg(not(x11_platform))]
panic!("x11 feature is not enabled")
}
"wayland" => {
#[cfg(wayland_platform)]
return EventLoop::new_wayland_any_thread()
.expect("Failed to initialize Wayland backend");
#[cfg(not(wayland_platform))]
panic!("wayland feature is not enabled");
}
_ => panic!(
"Unknown environment variable value for {BACKEND_PREFERENCE_ENV_VAR}, try one of `x11`,`wayland`",
),
// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
// variables are also treated as not set.
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
env::var("DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
) {
// User is forcing a backend.
(Some(backend), _, _) => backend,
// Wayland is present.
#[cfg(wayland_platform)]
(None, true, _) => Backend::Wayland,
// X11 is present.
#[cfg(x11_platform)]
(None, _, true) => Backend::X,
// No backend is present.
_ => {
return Err(EventLoopError::Os(os_error!(OsError::Misc(
"neither WAYLAND_DISPLAY nor DISPLAY is set."
))));
}
};
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)]
Backend::X => Ok(EventLoop::new_x11_any_thread().unwrap()),
}
#[cfg(wayland_platform)]
let wayland_err = match EventLoop::new_wayland_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(x11_platform)]
let x11_err = match EventLoop::new_x11_any_thread() {
Ok(event_loop) => return event_loop,
Err(err) => err,
};
#[cfg(not(wayland_platform))]
let wayland_err = "backend disabled";
#[cfg(not(x11_platform))]
let x11_err = "backend disabled";
panic!(
"Failed to initialize any backend! Wayland status: {wayland_err:?} X11 status: {x11_err:?}",
);
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
@@ -816,18 +807,25 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run_return<F>(&mut self, callback: F) -> i32
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
self.run_on_demand(callback)
}
pub fn run<F>(self, callback: F) -> !
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW<T>),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
}
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
@@ -835,6 +833,18 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
@@ -865,69 +875,78 @@ impl<T> EventLoopWindowTarget<T> {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp
.available_monitors()
.into_iter()
.map(MonitorHandle::Wayland)
.collect(),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp
.x_connection()
.available_monitors()
.into_iter()
.map(MonitorHandle::X)
.collect(),
}
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
match *self {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => {
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
Some(primary_monitor)
evlp.available_monitors().map(MonitorHandle::X).collect()
}
}
}
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {
match *self {
#[cfg(wayland_platform)]
EventLoopWindowTarget::Wayland(_) => (),
#[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => evlp.set_listen_device_events(_allowed),
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(
x11_or_wayland!(match self; EventLoopWindowTarget(evlp) => evlp.primary_monitor()?; as MonitorHandle),
)
}
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
#[inline]
pub fn listen_device_events(&self, allowed: DeviceEvents) {
x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed))
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06())
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow))
}
pub(crate) fn control_flow(&self) -> ControlFlow {
x11_or_wayland!(match self; Self(evlp) => evlp.control_flow())
}
pub(crate) fn exit(&self) {
x11_or_wayland!(match self; Self(evlp) => evlp.exit())
}
pub(crate) fn exiting(&self) -> bool {
x11_or_wayland!(match self; Self(evlp) => evlp.exiting())
}
fn set_exit_code(&self, code: i32) {
x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code))
}
fn exit_code(&self) -> Option<i32> {
x11_or_wayland!(match self; Self(evlp) => evlp.exit_code())
}
}
fn sticky_exit_callback<T, F>(
evt: Event<'_, T>,
target: &RootELW<T>,
control_flow: &mut ControlFlow,
callback: &mut F,
) where
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
{
// make ControlFlow::ExitWithCode sticky by providing a dummy
// control flow reference if it is already ExitWithCode.
if let ControlFlow::ExitWithCode(code) = *control_flow {
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
} else {
callback(evt, target, control_flow)
}
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| {
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
}
#[cfg(target_os = "linux")]
fn is_main_thread() -> bool {
use libc::{c_long, getpid, syscall, SYS_gettid};
unsafe { syscall(SYS_gettid) == getpid() as c_long }
rustix::thread::gettid() == rustix::process::getpid()
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]

View File

@@ -1,26 +1,30 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::error::Error;
use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::process;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
use sctk::reexports::calloop;
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use sctk::reexports::client::{Connection, QueueHandle};
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{
ControlFlow, DeviceEvents, EventLoopWindowTarget as RootEventLoopWindowTarget,
};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError};
mod proxy;
pub mod sink;
@@ -29,12 +33,20 @@ pub use proxy::EventLoopProxy;
use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::{DeviceId, WindowId};
use super::window::state::FrameCallbackState;
use super::{DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
buffer_sink: EventSink,
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
@@ -59,61 +71,110 @@ pub struct EventLoop<T: 'static> {
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
let connection = Connection::connect_to_env()?;
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
};
}
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let (globals, mut event_queue) = map_err!(
globals::registry_queue_init(&connection),
WaylandError::Global
)?;
let queue_handle = event_queue.handle();
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
let event_loop = map_err!(
calloop::EventLoop::<WinitState>::try_new(),
WaylandError::Calloop
)?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
event_queue.roundtrip(&mut winit_state)?;
map_err!(
event_queue.roundtrip(&mut winit_state),
WaylandError::Dispatch
)?;
// Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?;
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state)
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
let result = queue.dispatch_pending(winit_state);
if result.is_ok()
&& (!winit_state.events_sink.is_empty()
|| !winit_state.window_compositor_updates.is_empty())
{
winit_state.dispatched_events = true;
}
result
});
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
map_err!(
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone()),
WaylandError::Calloop
)?;
// Setup the user proxy.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
event_loop
let result = event_loop
.handle()
.insert_source(user_events_channel, move |event, _, _| {
if let calloop::channel::Event::Msg(msg) = event {
pending_user_events_clone.borrow_mut().push(msg);
}
})?;
.insert_source(
user_events_channel,
move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
},
)
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
// An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
event_loop
let (event_loop_awakener, event_loop_awakener_source) = map_err!(
calloop::ping::make_ping()
.map_err(|error| CalloopError::OtherError(Box::new(error).into())),
WaylandError::Calloop
)?;
let result = event_loop
.handle()
.insert_source(event_loop_awakener_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
})?;
.insert_source(
event_loop_awakener_source,
move |_, _, winit_state: &mut WinitState| {
// Mark that we have something to dispatch.
winit_state.dispatched_events = true;
},
)
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
let window_target = EventLoopWindowTarget {
connection: connection.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
event_loop_awakener,
queue_handle,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
state: RefCell::new(winit_state),
_marker: PhantomData,
};
let event_loop = Self {
loop_running: false,
compositor_updates: Vec::new(),
buffer_sink: EventSink::default(),
window_ids: Vec::new(),
connection,
wayland_dispatcher,
user_events_sender,
@@ -128,330 +189,339 @@ impl<T: 'static> EventLoop<T> {
Ok(event_loop)
}
pub fn run<F>(mut self, callback: F) -> !
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
let exit_code = self.run_return(callback);
process::exit(exit_code);
if self.loop_running {
return Err(EventLoopError::AlreadyRunning);
}
let exit = loop {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
}
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
}
_ => {
continue;
}
}
};
// Applications aren't allowed to carry windows between separate
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// compositor.
let _ = self.roundtrip().map_err(EventLoopError::Os);
exit
}
pub fn run_return<F>(&mut self, mut callback: F) -> i32
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
let mut control_flow = ControlFlow::Poll;
if !self.loop_running {
self.loop_running = true;
// XXX preallocate certian structures to avoid allocating on each loop iteration.
let mut window_ids = Vec::<WindowId>::new();
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
let mut buffer_sink = EventSink::new();
// Run the initial loop iteration.
self.single_iteration(&mut callback, StartCause::Init);
}
callback(
Event::NewEvents(StartCause::Init),
&self.window_target,
&mut control_flow,
);
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
callback(Event::LoopExiting, self.window_target());
// XXX We break on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Flush the connection.
let _ = self.connection.flush();
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
}
}
// During the run of the user callback, some other code monitoring and reading the
// Wayland socket may have been run (mesa for example does this with vsync), if that
// is the case, some events may have been enqueued in our event queue.
//
// If some messages are there, the event loop needs to behave as if it was instantly
// woken up by messages arriving from the Wayland socket, to avoid delaying the
// dispatch of these events until we're woken up again.
let instant_wakeup = {
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let queue = wayland_source.queue();
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => {
window_target.state.get_mut()
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
let cause = loop {
let start = Instant::now();
timeout = {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
#[cfg(x11_platform)]
_ => unreachable!(),
};
min_timeout(control_flow_timeout, timeout)
};
match queue.dispatch_pending(state) {
Ok(dispatched) => dispatched > 0,
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
break 1;
// NOTE Ideally we should flush as the last thing we do before polling
// to wait for events, and this should be done by the calloop
// WaylandSource but we currently need to flush writes manually.
//
// Checking for flush error is essential to perform an exit with error, since
// once we have a protocol error, we could get stuck retrying...
if self.connection.flush().is_err() {
self.set_exit_code(1);
return;
}
if let Err(error) = self.loop_dispatch(timeout) {
// NOTE We exit on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = error.raw_os_error().unwrap_or(1);
self.set_exit_code(exit_code);
return;
}
// NB: `StartCause::Init` is handled as a special case and doesn't need
// to be considered here
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}
} else {
StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}
}
}
};
match control_flow {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::ZERO;
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::Poll),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::ZERO)
} else {
None
};
if let Err(error) = self.loop_dispatch(timeout) {
break error.raw_os_error().unwrap_or(1);
}
callback(
Event::NewEvents(StartCause::WaitCancelled {
start: Instant::now(),
requested_resume: None,
}),
&self.window_target,
&mut control_flow,
);
}
ControlFlow::WaitUntil(deadline) => {
let start = Instant::now();
// Compute the amount of time we'll block for.
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::ZERO
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
break error.raw_os_error().unwrap_or(1);
}
let now = Instant::now();
if now < deadline {
callback(
Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}),
&self.window_target,
&mut control_flow,
)
} else {
callback(
Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}),
&self.window_target,
&mut control_flow,
)
}
}
// Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
continue;
}
// 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(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
break cause;
};
self.single_iteration(&mut callback, cause);
}
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>),
{
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
// for the sake of avoiding lots of reallocs. We take them here to avoid
// trying to mutably borrow `self` more than once and we swap them back
// when finished.
let mut compositor_updates = std::mem::take(&mut self.compositor_updates);
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
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 {
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(..) {
callback(Event::UserEvent(user_event), &self.window_target);
}
// Drain the pending compositor updates.
self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates));
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
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);
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw.
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
// Drain the pending compositor updates.
self.with_state(|state| {
compositor_updates.append(&mut state.window_compositor_updates)
});
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let mut physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
},
},
&self.window_target,
&mut control_flow,
&mut callback,
);
let new_logical_size = physical_size.to_logical(scale_factor);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
// TODO could probably bring back size reporting optimization.
// Mark the window as needed a redraw.
state
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
if compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
&mut control_flow,
&mut callback,
);
}
if compositor_update.close_window {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
}
}
// Push the events directly from the window.
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Push the events directly from the window.
self.with_state(|state| {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
}
// Send events cleared.
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
// Collect the window ids
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
});
// Collect the window ids
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut window = state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap();
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
if window.frame_callback_state() == FrameCallbackState::Requested {
false
} else {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Redraw the frames while at it.
redraw_requested |= state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap()
.refresh_frame();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
redraw_requested
}
});
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
);
}
});
if request_redraw {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::RedrawRequested,
},
&self.window_target,
);
}
}
// Send RedrawEventCleared.
sticky_exit_callback(
Event::RedrawEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);
};
// Reset the hint that we've dispatched events.
self.with_state(|state| {
state.dispatched_events = false;
});
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
exit_code
// This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target);
std::mem::swap(&mut self.compositor_updates, &mut compositor_updates);
std::mem::swap(&mut self.buffer_sink, &mut buffer_sink);
std::mem::swap(&mut self.window_ids, &mut window_ids);
}
#[inline]
@@ -486,6 +556,50 @@ impl<T: 'static> EventLoop<T> {
error.into()
})
}
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(feature = "x11")]
_ => unreachable!(),
};
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
let event_queue = wayland_source.queue();
event_queue.roundtrip(state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
error
))))
})
}
fn control_flow(&self) -> ControlFlow {
self.window_target.p.control_flow()
}
fn exiting(&self) -> bool {
self.window_target.p.exiting()
}
fn set_exit_code(&self, code: i32) {
self.window_target.p.set_exit_code(code)
}
fn exit_code(&self) -> Option<i32> {
self.window_target.p.exit_code()
}
}
impl<T> AsFd for EventLoop<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub struct EventLoopWindowTarget<T> {
@@ -495,6 +609,12 @@ pub struct EventLoopWindowTarget<T> {
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
/// The application's latest control_flow state
pub(crate) control_flow: Cell<ControlFlow>,
/// The application's exit state.
pub(crate) exit: Cell<Option<i32>>,
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
/// Winit state.
pub state: RefCell<WinitState>,
@@ -509,10 +629,31 @@ pub struct EventLoopWindowTarget<T> {
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
use sctk::reexports::client::Proxy;
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.connection.display().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null")
})
.into())
}
}

View File

@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub window_events: Vec<Event<'static, ()>>,
pub window_events: Vec<Event<()>>,
}
impl EventSink {
@@ -20,6 +20,12 @@ impl EventSink {
Default::default()
}
/// Return `true` if there're pending events.
#[inline]
pub fn is_empty(&self) -> bool {
self.window_events.is_empty()
}
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
@@ -31,7 +37,7 @@ impl EventSink {
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
@@ -44,7 +50,7 @@ impl EventSink {
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
self.window_events.drain(..)
}
}

View File

@@ -2,10 +2,14 @@
//! Winit's Wayland backend.
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use std::fmt::Display;
use std::sync::Arc;
pub use crate::platform_impl::platform::WindowId;
use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
pub use crate::platform_impl::platform::{OsError, WindowId};
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
@@ -17,6 +21,46 @@ mod state;
mod types;
mod window;
#[derive(Debug)]
pub enum WaylandError {
/// Error connecting to the socket.
Connection(ConnectError),
/// Error binding the global.
Global(GlobalError),
// Bind error.
Bind(BindError),
/// Error during the dispatching the event queue.
Dispatch(DispatchError),
/// Calloop error.
Calloop(calloop::Error),
/// Wayland
Wire(client::backend::WaylandError),
}
impl Display for WaylandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WaylandError::Connection(error) => error.fmt(f),
WaylandError::Global(error) => error.fmt(f),
WaylandError::Bind(error) => error.fmt(f),
WaylandError::Dispatch(error) => error.fmt(f),
WaylandError::Calloop(error) => error.fmt(f),
WaylandError::Wire(error) => error.fmt(f),
}
}
}
impl From<WaylandError> for OsError {
fn from(value: WaylandError) -> Self {
Self::WaylandError(Arc::new(value))
}
}
/// Dummy device id, since Wayland doesn't have device events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;

View File

@@ -3,29 +3,51 @@ use sctk::reexports::client::Proxy;
use sctk::output::OutputData;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl::platform::{
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
};
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::event_loop::ControlFlow;
use crate::platform_impl::platform::VideoMode as PlatformVideoMode;
use super::event_loop::EventLoopWindowTarget;
impl<T> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.state
.borrow()
.output_state
.outputs()
.map(MonitorHandle::new)
.collect()
}
#[inline]
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// There's no primary monitor on Wayland.
None
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub(crate) fn exit(&self) {
self.exit.set(Some(0))
}
pub(crate) fn exiting(&self) -> bool {
self.exit.get().is_some()
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
}
#[derive(Clone, Debug)]
@@ -70,7 +92,18 @@ impl MonitorHandle {
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.location).into()
output_data.with_output_info(|info| {
info.logical_position.map_or_else(
|| {
LogicalPosition::<i32>::from(info.location)
.to_physical(info.scale_factor as f64)
},
|logical_position| {
LogicalPosition::<i32>::from(logical_position)
.to_physical(info.scale_factor as f64)
},
)
})
}
#[inline]
@@ -157,7 +190,7 @@ impl VideoMode {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> PlatformMonitorHandle {
PlatformMonitorHandle::Wayland(self.monitor.clone())
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}

View File

@@ -67,7 +67,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
};
// Drop the repeat, if there were any.
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
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);
}
// The keyboard focus is considered as general focus.
state
@@ -89,7 +93,11 @@ 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.
seat_state.keyboard_state.as_mut().unwrap().current_repeat = None;
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);
}
// NOTE: The check whether the window exists is essential as we might get a
// nil surface, regardless of what protocol says.
@@ -114,9 +122,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
}
WlKeyboardEvent::Key {
key,
state: key_state,
state: WEnum::Value(WlKeyState::Pressed),
..
} if key_state == WEnum::Value(WlKeyState::Pressed) => {
} => {
let key = key + 8;
key_input(
@@ -151,6 +159,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
keyboard_state.repeat_token = keyboard_state
.loop_handle
.insert_source(timer, move |_, _, state| {
// Required to handle the wakeups from the repeat sources.
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
@@ -181,9 +192,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
}
WlKeyboardEvent::Key {
key,
state: key_state,
state: WEnum::Value(WlKeyState::Released),
..
} if key_state == WEnum::Value(WlKeyState::Released) => {
} => {
let key = key + 8;
key_input(
@@ -201,6 +212,9 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
&& Some(key) == keyboard_state.current_repeat
{
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
}
}
}
WlKeyboardEvent::Modifiers {

View File

@@ -2,7 +2,7 @@
use std::sync::Arc;
use fnv::FnvHashMap;
use ahash::AHashMap;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -29,7 +29,7 @@ use keyboard::{KeyboardData, KeyboardState};
use text_input::TextInputData;
use touch::TouchPoint;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct WinitSeatState {
/// The pointer bound on the seat.
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
@@ -38,7 +38,7 @@ pub struct WinitSeatState {
touch: Option<WlTouch>,
/// The mapping from touched points to the surfaces they're present.
touch_map: FnvHashMap<i32, TouchPoint>,
touch_map: AHashMap<i32, TouchPoint>,
/// The text input bound on the seat.
text_input: Option<Arc<ZwpTextInputV3>>,
@@ -58,16 +58,7 @@ pub struct WinitSeatState {
impl WinitSeatState {
pub fn new() -> Self {
Self {
pointer: None,
touch: None,
relative_pointer: None,
text_input: None,
touch_map: Default::default(),
keyboard_state: None,
modifiers: ModifiersState::empty(),
modifiers_pending: false,
}
Default::default()
}
}
@@ -97,12 +88,14 @@ impl SeatHandler for WinitState {
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone(), surface);
let pointer_data = WinitPointerData::new(seat.clone());
let themed_pointer = self
.seat_state
.get_pointer_with_theme_and_data(
queue_handle,
&seat,
self.shm.wl_shm(),
surface,
ThemeSpec::System,
pointer_data,
)
@@ -167,7 +160,7 @@ impl SeatHandler for WinitState {
let pointer_data = pointer.pointer().winit_data();
// Remove the cursor from the mapping.
let surface_id = pointer_data.cursor_surface().id();
let surface_id = pointer.surface().id();
let _ = self.pointer_surfaces.remove(&surface_id);
// Remove the inner locks/confines before dropping the pointer.

View File

@@ -2,6 +2,7 @@
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
@@ -10,15 +11,17 @@ use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::csd_frame::FrameClick;
use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData;
use sctk::seat::pointer::{PointerData, PointerDataExt};
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
use sctk::seat::SeatState;
use sctk::shell::xdg::frame::FrameClick;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
@@ -67,35 +70,31 @@ impl PointerHandler for WinitState {
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
if parent_surface != surface =>
{
if let Some(icon) =
window.frame_point_moved(surface, event.position.0, event.position.1)
{
if let Some(icon) = window.frame_point_moved(
seat,
surface,
Duration::ZERO,
event.position.0,
event.position.1,
) {
if let Some(pointer) = seat_state.pointer.as_ref() {
let surface = pointer
.pointer()
.data::<WinitPointerData>()
.unwrap()
.cursor_surface();
let scale_factor =
surface.data::<SurfaceData>().unwrap().scale_factor();
let _ = pointer.set_cursor(
connection,
icon,
self.shm.wl_shm(),
surface,
scale_factor,
);
let _ = pointer.set_cursor(connection, icon);
}
}
}
PointerEventKind::Leave { .. } if parent_surface != surface => {
window.frame_point_left();
}
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. }
if parent_surface != surface =>
{
ref kind @ PointerEventKind::Press {
button,
serial,
time,
}
| ref kind @ PointerEventKind::Release {
button,
serial,
time,
} if parent_surface != surface => {
let click = match wayland_button_to_winit(button) {
MouseButton::Left => FrameClick::Normal,
MouseButton::Right => FrameClick::Alternate,
@@ -109,6 +108,7 @@ impl PointerHandler for WinitState {
pressed,
seat,
serial,
Duration::from_millis(time as u64),
window_id,
&mut self.window_compositor_updates,
);
@@ -238,9 +238,6 @@ impl PointerHandler for WinitState {
#[derive(Debug)]
pub struct WinitPointerData {
/// The surface associated with this pointer, which is used for icons.
cursor_surface: WlSurface,
/// The inner winit data associated with the pointer.
inner: Mutex<WinitPointerDataInner>,
@@ -249,9 +246,8 @@ pub struct WinitPointerData {
}
impl WinitPointerData {
pub fn new(seat: WlSeat, surface: WlSurface) -> Self {
pub fn new(seat: WlSeat) -> Self {
Self {
cursor_surface: surface,
inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat),
}
@@ -313,11 +309,6 @@ impl WinitPointerData {
self.sctk_data.seat()
}
/// The WlSurface used to set cursor theme.
pub fn cursor_surface(&self) -> &WlSurface {
&self.cursor_surface
}
/// Active window.
pub fn focused_window(&self) -> Option<WindowId> {
self.inner.lock().unwrap().surface
@@ -325,7 +316,7 @@ impl WinitPointerData {
/// Last button serial.
pub fn latest_button_serial(&self) -> u32 {
self.inner.lock().unwrap().latest_button_serial
self.sctk_data.latest_button_serial().unwrap_or_default()
}
/// Last enter serial.
@@ -341,12 +332,6 @@ impl WinitPointerData {
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
self.cursor_surface.destroy();
}
}
impl PointerDataExt for WinitPointerData {
fn pointer_data(&self) -> &PointerData {
&self.sctk_data
@@ -486,7 +471,35 @@ impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstrain
}
}
impl Dispatch<WpCursorShapeDeviceV1, GlobalData, WinitState> for SeatState {
fn event(
_: &mut WinitState,
_: &WpCursorShapeDeviceV1,
_: <WpCursorShapeDeviceV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("wp_cursor_shape_manager has no events")
}
}
impl Dispatch<WpCursorShapeManagerV1, GlobalData, WinitState> for SeatState {
fn event(
_: &mut WinitState,
_: &WpCursorShapeManagerV1,
_: <WpCursorShapeManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
unreachable!("wp_cursor_device_manager has no events")
}
}
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState);
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);

View File

@@ -121,7 +121,7 @@ impl TouchHandler for WinitState {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
phase: TouchPhase::Moved,
location: touch_point.location.to_physical(scale_factor),
force: None,
id: id as u64,

View File

@@ -1,8 +1,8 @@
use std::cell::RefCell;
use std::error::Error;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use fnv::FnvHashMap;
use ahash::AHashMap;
use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId;
@@ -23,6 +23,7 @@ use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::dpi::LogicalSize;
use crate::platform_impl::OsError;
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
@@ -30,11 +31,12 @@ use super::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
use super::types::kwin_blur::KWinBlurManager;
use super::types::wp_fractional_scaling::FractionalScalingManager;
use super::types::wp_viewporter::ViewporterState;
use super::types::xdg_activation::XdgActivationState;
use super::window::{WindowRequests, WindowState};
use super::WindowId;
use super::{WaylandError, WindowId};
/// Winit's Wayland state.
pub struct WinitState {
@@ -60,10 +62,10 @@ pub struct WinitState {
pub xdg_shell: XdgShell,
/// The currently present windows.
pub windows: RefCell<FnvHashMap<WindowId, Arc<Mutex<WindowState>>>>,
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
pub window_requests: RefCell<FnvHashMap<WindowId, Arc<WindowRequests>>>,
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
/// The events that were generated directly from the window.
pub window_events_sink: Arc<Mutex<EventSink>>,
@@ -72,10 +74,10 @@ pub struct WinitState {
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
/// Currently handled seats.
pub seats: FnvHashMap<ObjectId, WinitSeatState>,
pub seats: AHashMap<ObjectId, WinitSeatState>,
/// Currently present cursor surfaces.
pub pointer_surfaces: FnvHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
/// The state of the text input on the client.
pub text_input_state: Option<TextInputState>,
@@ -102,8 +104,15 @@ pub struct WinitState {
/// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// KWin blur manager.
pub kwin_blur_manager: Option<KWinBlurManager>,
/// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>,
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
}
impl WinitState {
@@ -111,21 +120,23 @@ impl WinitState {
globals: &GlobalList,
queue_handle: &QueueHandle<Self>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Result<Self, Box<dyn Error>> {
) -> Result<Self, OsError> {
let registry_state = RegistryState::new(globals);
let compositor_state = CompositorState::bind(globals, queue_handle)?;
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
)?;
)
.map_err(WaylandError::Bind)?;
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
let seat_state = SeatState::new(globals, queue_handle);
let mut seats = FnvHashMap::default();
let mut seats = AHashMap::default();
for seat in seat_state.seats() {
seats.insert(seat.id(), WinitSeatState::new());
}
@@ -143,9 +154,9 @@ impl WinitState {
subcompositor_state: Arc::new(subcompositor_state),
output_state,
seat_state,
shm: Shm::bind(globals, queue_handle)?,
shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_shell: XdgShell::bind(globals, queue_handle)?,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(),
@@ -154,6 +165,7 @@ impl WinitState {
window_events_sink: Default::default(),
viewporter_state,
fractional_scaling_manager,
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(),
@@ -167,6 +179,8 @@ impl WinitState {
monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(),
loop_handle,
// Make it true by default.
dispatched_events: true,
})
}
@@ -273,7 +287,12 @@ impl WindowHandler for WinitState {
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(configure, &self.shm, &self.subcompositor_state);
.configure(
configure,
&self.shm,
&self.subcompositor_state,
&mut self.events_sink,
);
self.window_compositor_updates[pos].size = Some(new_size);
}
@@ -311,6 +330,16 @@ impl OutputHandler for WinitState {
}
impl CompositorHandler for WinitState {
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn scale_factor_changed(
&mut self,
_: &Connection,
@@ -321,7 +350,27 @@ impl CompositorHandler for WinitState {
self.scale_factor_changed(surface, scale_factor as f64, true)
}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, _: u32) {
let window_id = super::make_wid(surface);
let window = match self.windows.get_mut().get(&window_id) {
Some(window) => window,
None => return,
};
// In case we have a redraw requested we must indicate the wake up.
if self
.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.load(Ordering::Relaxed)
{
self.dispatched_events = true;
}
window.lock().unwrap().frame_callback_received();
}
}
impl ProvidesRegistryState for WinitState {

Some files were not shown because too many files have changed in this diff Show More