Compare commits

..

70 Commits

Author SHA1 Message Date
John Nunley
19477d3f03 doc: Add installation instructions to docs
This commit adds installation instructions to the top-level
documentation for lib.rs. The main goal is to give the dependencies that
X11/Wayland use in a formal place, since it's not written down anywhere
to my knowledge.

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-08 08:30:31 -08:00
Mads Marquart
e47081e385 docs: show less of the unstable docs in the README 2024-12-06 13:16:30 +03:00
Benjamin Brienen
171d53c042 Update smol_str (#3991) 2024-12-03 23:07:19 +01:00
Mads Marquart
35379f305a Remove feature description in FEATURES.md (#3479)
It easily becomes out of date.
2024-12-03 21:24:57 +01:00
Mads Marquart
4d2a0dd2b3 iOS: Never queue application-level events (#3905)
Events like `resumed`, `new_events`, `about_to_wait`, and so on will
never happen as a result of programmer action, so we'll never need to
queue those. This allows us to remove the need for the old `Event`
struct in the iOS backend.

Furthermore, we can now remove `InUserCallback`, since that state is
already stored inside `EventHandler`.

I've tried to otherwise keep the semantics as close to the original by
calling `handle_nonuser_events(mtm, [])`, which flushes pending events.
2024-12-03 19:53:29 +01:00
Mads Marquart
4a8b659228 Fix MonitorHandle PartialEq and Hash on iOS (#4013) 2024-12-03 19:02:53 +01:00
Mads Marquart
f314cd2b9a macOS: Fix crash when pressing Caps Lock (#4024)
Events emitted by `flagsChanged:` cannot access
`charactersIgnoringModifiers`. We were previously doing this because we
were trying to re-use the `create_key_event` function, but that is unsuited
for this purpose, so I have separated the `flagsChanged:` logic out from it.
2024-12-03 18:48:23 +01:00
Mads Marquart
3657506f6e macOS: Avoid redundant initial resize event (#3913)
The `NSViewFrameDidChangeNotification` that we listen to is emitted when
`-[NSWindow setContentView]` is called, since that sets the frame of the
view as well.

So now we register the notification later, so that it's not triggered at
window creation.

This behaviour is well described in the documentation:
https://developer.apple.com/documentation/appkit/nsview/postsframechangednotifications?language=objc
2024-12-03 18:35:04 +01:00
Mads Marquart
edca3ebc41 macOS: Align scancode conversions with Chromium and Firefox (#4019)
Also fix missing codes in physicalkey_to_scancode - This had become out of
sync with scancode_to_physicalkey.
2024-12-03 18:17:57 +01:00
Mads Marquart
ca46e29203 macOS: Fix surface position (#4027) 2024-12-03 17:31:32 +01:00
Mads Marquart
132fbe14d5 macOS: Fix safe area on macOS 10.14 and below (#4028) 2024-12-03 17:19:45 +01:00
Mads Marquart
164bf85b5b Windows: Add scancode conversions from Chromium sources (#4020) 2024-12-03 11:05:22 +01:00
Mads Marquart
cfa8f027cc AppKit/UIKit: Do not emit default state events upon window creation (#3912)
Do not emit redundant ScaleFactorChanged, SurfaceResized and Focused.
2024-12-02 23:10:33 +01:00
Mads Marquart
2e5db75101 Linux: Align scancode conversions with Firefox (#4021) 2024-12-02 13:38:37 +01:00
Mads Marquart
19e5bee3d1 chore: fix clippy lints 2024-12-02 14:51:26 +03:00
Hamir Mahal
fc6cf89ac0 style: simplify string formatting for readability (#4001) 2024-11-22 22:14:11 +01:00
Mads Marquart
dbcdb6f1b4 Add safe area and document coordinate systems (#3890)
Added `Window::safe_area`, which describes the area of the surface that
is unobstructed by notches, bezels etc. The drawing code in the examples
have been updated to draw a star inside the safe area, and the plain
background outside of it.

Also renamed `Window::inner_position` to `Window::surface_position`, and
changed it to from screen coordinates to window coordinates, to better
align how these coordinate systems work together.

Finally, added some SVG images and documentation to describe how all of
this works.

This is fully implemented on macOS and iOS, and partially on the web.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-11-21 17:37:03 +01:00
Rodrigo Rivas Costa
d0c6c34eaa x11: move up XInput2 event registration
It should be done before mapping the window, or we could
lose the firsst XInput2 events, such as the first FocusIn.

Fixes #2841.
2024-11-21 14:04:55 +03:00
Mads Marquart
f2688d94ea ci: fix cargo deny 2024-11-21 10:28:32 +03:00
Nico Burns
f6b20852bf macOS: set activation policy by default if app is not bundled (#3961) 2024-11-18 21:19:45 +01:00
Kirill Chibisov
59b1eb5410 api: make OwnedDisplayHandle wrap an opaque type
This will help in case we want to linger the event loop during drop
to prevent use after free in the consumers code.
2024-11-13 15:29:05 +03:00
Aaron Muir Hamilton
f781e13166 X11: Use bottom-right corner of IME cursor area as caret position
XIM servers currently do not support preedit area reporting from
clients and there may be no standard way to report it.

Fcitx and iBus both place the candidate window descending descending
from the caret, with the reported X font; but since winit does not
report a font, the height of the line is assumed 0, so when we report
the top left corner of the cursor area they will tend to obscure it.

Taking this into account, the best default option is to report the
bottom right corner of the cursor area, because it will tend not to
obscure the preedit area when using `Window::set_ime_cursor_area` in
the way suggested by documentation.
2024-11-13 15:13:09 +03:00
Piotr Podusowski
9f8ac8feb5 android: use show_soft_input to summon the keyboard
Route it via the `Window::set_ime_allowed` like on iOS.
2024-11-13 14:43:59 +03:00
Mads Marquart
74958ecc6f docs: add fn main to root examples
This is not strictly required, but makes the examples a bit easier to
read understand (especially since the `EventLoop` really _should_ be
created inside `fn main`, and not in some random function potentially
running on a random thread).
2024-11-12 16:52:48 +03:00
Kirill Chibisov
3a60cbaba5 api: make EventLoopProxy wrap an opaque type
The proxy is intended to be Clone, thus use `Arc` for it internally and
don't require backends for it to be `Clone`. Use `EventLoopProxyProvider`
to hide the backend's proxy implementation details.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-11-12 10:56:20 +03:00
Matěj Laitl
ae4c449670 x11: fix WindowAttributesExtX11::with_x11_screen()
Based on https://github.com/rust-windowing/winit/pull/3973, which should be merged first.

There's an API to programmatically specify X11 screen id (override what is determined from the `DISPLAY` env variable), but it doesn't work.

Seeting up X Server with 2 screens and calling `DISPLAY=:0 X11_SCREEN_ID=1 cargo run --example window` should be equivalent to calling `DISPLAY=:0.1 cargo run --example window`

The latter works (and places the window on the correct screen), but the former yields

`failed to create initial window: Os(OsError { line: 620, file: "src/platform_impl/linux/x11/window.rs", error: X11Error(X11Error { error_kind: Match, error_code: 8, sequence: 219, bad_value: 1319, minor_opcode: 0, major_opcode: 1, extension_name: None, request_name: Some("CreateWindow") }) })`

_Here `1319` is the root window id for screen 0, which doesn't match the screen 1 that we request._

The problem is that we need to factor in the screen id when determining the parent (root) window when not explicitly set. This patch does that.

---

Also: Extend the window example with X11_{SCREEN,VISUAL}_ID env variables
2024-11-06 08:30:25 -08:00
Kirill Chibisov
b2896d7408 chore: always pull raw-window-handle
Winit is not useful without it and we don't provide older versions
anymore.
2024-11-03 00:39:01 +03:00
Kirill Chibisov
edfb4b03f4 chore: remove platform FingerId
The same as for `WindowId`.
2024-11-02 17:10:32 +03:00
Kirill Chibisov
c8c1eca3c7 api: move primary from FingerId to Pointer events
Whether the pointer event is primary or not generally matters for the
context where all input is done by the same event, so users can
_ignore_ non-primary events since they are likely from users clicking
something else with their other fingers.

Having it only on a FingerId made it useless, since it's usually used
to avoid multi-touch, but if you start mapping on touch event you
already can track things like that yourself.

Fixes #3943.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-11-02 17:10:32 +03:00
Matěj Laitl
d3207a8d76 Apply @notgull's suggestion 2024-10-29 17:54:21 -07:00
Matěj Laitl
9d347b72d9 Add changelog entry 2024-10-29 17:54:21 -07:00
Matěj Laitl
13bb51c7e0 x11: iterate only visuals from the matching screen 2024-10-29 17:54:21 -07:00
AngelicosPhosphoros
bb4aa22cf9 Improve waiting for messages on Windows
Previous version used [`SetTimer`] with `GetMessageW` for waiting.
The downside of UI timers like ones created by `SetTimer`,
is that they may be late by up to 15-16 ms.

To fix this behaviour, I added use of high resolution timers created by [`CreateWaitableTimerExW`] with the flag `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
In my previous experience, waiting on such timers have precision of roundly 0.5 ms which is the best available on Windows at the moment.
I use [`MsgWaitForMultipleObjectsEx`] to wait simultaneously for both timer and newly arriving events.

Unfortunately, high resolution timers are available only since Windows 10 1803. However:

1. Win 10 is already getting to the end of support, like all previous versions, so it is OK to rely on APIs introduced in it;
2. I use `dwMilliseconds` parameter of `MsgWaitForMultipleObjectsEx` as a fallback. It should perform not worse compared to waiting for events from `SetTimer`.

I also refactored code to remove event dispatching from function responsible for waiting for events. This provides more clear separations of concern and avoids unnecessary duplication of dispatching logic.

After [review] from @rib, I also moved the waiting itself from `wait_for_messages` method to separate function, so it is clearly seen that `wait_for_messages` do 3 things: notify app that we about to wait, wait, notify that we have new events.

I have tested behaviour using a egui app with Vulkan rendering with `VK_PRESENT_MODE_IMMEDIATE_KHR`, and older version consistently have twice less FPS than requested (e.g. 30 FPS when limit is 60 and 60 FPS when limit is 120) while newer version works more correctly (almost always 60 FPS when limit is 60, and only 5-10 frames missing when FPS is set to 120 or more).

Fixes https://github.com/rust-windowing/winit/issues/1610

[`CreateWaitableTimerExW`]: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw
[`MsgWaitForMultipleObjectsEx`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex
[`SetTimer`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer
[review]: https://github.com/rust-windowing/winit/pull/3950#discussion_r1800184479
2024-10-28 16:38:17 -07:00
Valentine Briese
3e9b80d47a macOS: add ability to make titlebar unified (#3960)
Adds `WindowExtMacOS::set_unified_titlebar` and
`WindowAttributesExtMacOS::with_unified_titlebar`,
which allow you to use a larger titlebar style on macOS.
2024-10-25 23:22:52 +02:00
Nico Burns
c913cdab0b macOS: add a way to hook standard keybinding events
Add macOS specific application handler to deliver macOS specific
events.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-10-23 08:33:42 +00:00
Shane Celis
a5f5ce6a3d macOS: fix panic during drag_window
Return error from it instead of unwrapping.
2024-10-22 11:00:53 +00:00
Kirill Chibisov
fb6b1d487b deps: bump redox-syscall to 0.5.7 2024-10-21 21:33:51 +00:00
Marijn Suijten
7d77ccfad3 android: Forward suspended() and resumed() events and patch up platform-specific documentation (#3786)
Key them off of `onStop()` and `onStart()` which seems to match the
other backends most closely.  These [Android Activity lifecycle] events
denote when the application is visible on-screen, and recommend that any
heavy lifting for startup and shutdown happens here, as the application
may be demoted to the background and later shut down entirely unless the
user navigates back to it.

[Android Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
2024-10-16 12:17:59 +02:00
Kirill Chibisov
c23bed20b8 x11: don't forward key events to IME when it's disabled
Fixes #3815.
2024-10-13 21:57:02 +03:00
Hiroaki Yutani
8fe2b62adf docs: fix function name of with_any_thread 2024-10-13 21:34:11 +03:00
Kirill Chibisov
4e3165f3d8 chore: remove platform DeviceId
The same as for `WindowId`.
2024-10-11 11:15:54 +03:00
Mads Marquart
da2268ae22 chore: remove platform WindowId's
WindowId is a window _identifier_, and as such doesn't store anything
(unlike a _handle_). So we can safely make only be defined once, in the
core crate.

There are a few backends where we still use `into_raw` internally; I
consider these patterns discouraged, we should not be passing around
important state in the window id.
2024-10-08 16:29:40 +03:00
daxpedda
eccd9e415d api: overhaul pointer API
- Rename `CursorMoved` to `PointerMoved`.
- Rename `CursorEntered` to `PointerEntered`.
- Rename `CursorLeft` to `PointerLeft`.
- Rename `MouseInput` to `PointerButton`.
- Add `position` to every `PointerEvent`.
- Remove `Touch`, which is folded into the `Pointer*` events.
- New `PointerType` added to `PointerEntered` and `PointerLeft`,
  signifying which pointer type is the source of this event.
- New `PointerSource` added to `PointerMoved`, similar to `PointerType`
  but holding additional data.
- New `ButtonSource` added to `PointerButton`, similar to `PointerType`
  but holding pointer type specific buttons. Use
  `ButtonSource::mouse_button()` to easily normalize any pointer button
  type to a generic mouse button.
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
- Remove `Force::Calibrated::altitude_angle`.

Fixes #3833.
Fixes #883.
Fixes #336.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-10-08 15:19:00 +03:00
daxpedda
32cd1ad9a7 api: remove ::dummy from Id types
`::dummy` was used for testing purposes and became redundant after
adding e.g. `from_raw` and `into_raw` methods on `Id` types.
2024-09-29 16:49:45 +03:00
daxpedda
6e1b9fa24d api: replace WindowId From/Into u64 with WindowId::{from,into}_raw()
Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-09-27 23:12:50 +03:00
Mads Marquart
380eea0072 macOS: Fix move event sometimes being triggered on resize (#3914) 2024-09-24 01:06:10 +02:00
Kirill Chibisov
a18658284c chore: fix nightly CI on linux/ios
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-09-23 22:26:21 +03:00
Philpax
8ddd10a7f9 doc: link DeviceEvent::MouseMotion from cursor moved 2024-09-22 17:02:40 +03:00
John Nunley
4f1c5b6129 docs: Explicitly demarcate platform support
As discussed in today's meeting, this commit creates two tiers of
support. Tier 1, which is what we actively test, and Tier 2, what we
hope compiles.

Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-09-19 17:09:10 -07:00
purajit
7e819bb2ce Prevent winit from overriding LSUIElement in package manifests (#3920) 2024-09-16 15:49:18 +02:00
Mads Marquart
dfea49f488 Support drawing on iOS in examples
Softbuffer v0.4.6 added support for iOS.
2024-09-10 17:36:04 +03:00
Kirill Chibisov
b674d20edf api: unify error handling
Make error infrastructure more backend agnostic and let backends
just forward the os errors opaquely.
2024-09-06 17:20:11 +03:00
Mads Marquart
8db3e0e043 Rename "inner size" to "surface size" (#3889)
* Rename `WindowEvent::Resized` to `SurfaceResized`
* Rename `InnerSizeWriter` to `SurfaceSizeWriter`
* Replace `inner_size` with `surface_size`
* Rename `resize_increments` to `surface_resize_increments`
2024-09-04 15:04:48 +02:00
Tarek Abdel Sater
d37c591378 macOS: add option to explicitly hide menu/dock in Borderless (#3882) 2024-09-04 14:44:05 +02:00
Bruce Mitchener
9419e4e1a7 Fix spelling of "inner" (#3896) 2024-09-01 23:31:45 +02:00
Kirill Chibisov
241b7a80bb api: convert Window to dyn Window
This should allow us to make future split of backends much easier.
The `Box<dyn Window>` is a _temporary_ solution, which will be
removed with the future updates when we decide on how the Window
should be stored.
2024-08-23 23:40:27 +03:00
John Nunley
e716adcc0a x11: use more information in X11 "not supported" errors
This makes it so, when X11 fails to initialize due to not loading a
library, it provides more verbose information on what exactly happened.

Fixes #3883.
Signed-off-by: John Nunley <dev@notgull.net>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-08-23 14:47:40 +03:00
John Nunley
aee95114db m: Replace libxcursor with custom cursor code
Another one bites the dust.

This replaces the code dependent on libxcursor with equivalent code
written using x11rb, featuring its special "cursor" module.

cc #3198

Signed-off-by: John Nunley <dev@notgull.net>
2024-08-22 19:30:43 -07:00
John Nunley
8f4a8efa99 m: Ignore mutex poisoning in X11_BACKEND
A panic doesn't really put any of the fields in XConnection into an invalid
state, so there is no real reason to panic when poisoning is detected.
So just ignore the poison.

Closes #3870

Signed-off-by: John Nunley <dev@notgull.net>
2024-08-22 18:03:22 -07:00
lucasmerlin
1e1f0fd7e9 Basic iOS IME support (#3823)
This implements basic iOS IME support (typing, backspace, support for emojis
etc but no autocomplete or copy / paste menu).

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-08-19 22:04:29 +02:00
Mads Marquart
6e008b39e9 Improve iOS documentation (#3873)
* Update version docs to link to `rustc`'s supported versions

* Document how to run Winit on Mac Catalyst

* Improve instructions for building iOS applications

The old instructions are outdated, and suggested a workaround that is
unnecessary.

The user-story in the ecosystem is sadly not very clear-cut, so the
instructions here are still woefully incomplete.

* iOS: Clean up notes on main thread safety

These platform-specific notes on `Window` methods were unnecessary, as
it's already discussed in the top-level `Window` docs.
2024-08-19 12:41:29 +02:00
Mads Marquart
6c4da19197 macOS/iOS: Remove window activation hacks (#3872)
No longer necessary after a8c7109 and facb809.
2024-08-18 23:50:10 +02:00
Mads Marquart
a61e7bb4f4 iOS: Refactor event handling to share code with macOS (#3865)
Instead of storing the event handler within the AppState, and extracting
it our every time we need it, we now use the same event handling
implementation as for macOS that ensures we don't re-entrantly call the
event handler, and that we un-register the handler again after we're
done using it (`UIApplicationMain` won't return, but may still unwind,
so this is very important for panic safety).
2024-08-15 23:19:57 +02:00
daxpedda
7fbc2118b6 Add support for MacOS ARM64 (#3862) 2024-08-13 23:01:17 +02:00
daxpedda
a96491f302 Web: return MonitorHandle in Window::fullscreen() (#3861)
- Change `OrientationData::natural` type from `bool` to `Orientation`, just telling the user what the natural orientation is.
- Fix and improve some monitor related documentation.
2024-08-13 22:13:12 +02:00
daxpedda
d96fd02f33 Update minimum version of wasm-bindgen (#3860) 2024-08-13 21:54:03 +02:00
Mads Marquart
92e9bfe0fc Allow the user to register the application delegate on macOS and iOS (#3758)
This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.

To do this, we use notifications instead of top-level application delegate
methods.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice, since the
return value of `application:didFinishLaunchingWithOptions:` is seldom used by
the system (and is likely the preferred behaviour anyhow).
2024-08-11 23:14:18 +02:00
Kirill Chibisov
3392e9c1de chore: use our own AsAny to provide &dyn Any
This removes the direct requirement to implement `as_any` and it could
be just derived.

Also implement HasDisplayHandle for dyn ActiveEventLoop + '_.

Suggested-by: daxpedda <daxpedda@gmail.com>
2024-08-11 21:24:34 +03:00
Mads Marquart
038ef5c3ad Properly implement event loop extension traits
This was implemented for `&dyn ActiveEventLoop` before, but that's less
general than implementing it directly for `dyn ActiveEventLoop`.

The `+ '_` is required to tell the compiler that we want to implement
this for all `dyn ActiveEventLoop`s, not just `'static` ones (even
though they're all going to be `'static`).
2024-08-11 19:33:12 +03:00
Kirill Chibisov
70c54ee0ff Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-08-08 21:40:08 +03:00
125 changed files with 8443 additions and 7390 deletions

View File

@@ -2,4 +2,3 @@
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -68,7 +68,8 @@ jobs:
- { 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: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-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, }
@@ -243,10 +244,11 @@ jobs:
- { 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: 'macOS', target: aarch64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps:
- uses: taiki-e/checkout-action@v1

View File

@@ -21,11 +21,10 @@ name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.4"
version = "0.30.5"
[package.metadata.docs.rs]
features = [
"rwh_06",
"serde",
"mint",
# Enabled to get docs to compile
@@ -38,12 +37,13 @@ targets = [
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
"aarch64-apple-ios",
# Android
"aarch64-linux-android",
# Web
@@ -54,9 +54,8 @@ targets = [
[features]
android-game-activity = ["android-activity/game-activity"]
android-native-activity = ["android-activity/native-activity"]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
mint = ["dpi/mint"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
wayland = [
"wayland-client",
@@ -80,9 +79,9 @@ cfg_aliases = "0.2.1"
bitflags = "2"
cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }
[dev-dependencies]
@@ -90,8 +89,8 @@ image = { version = "0.25.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = { version = "0.4.0", default-features = false, features = [
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer = { version = "0.4.6", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
@@ -101,16 +100,16 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
block2 = "0.5.1"
core-graphics = "0.23.1"
objc2-app-kit = { version = "0.2.2", features = [
"NSAppearance",
@@ -135,6 +134,7 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
@@ -149,9 +149,11 @@ objc2-foundation = { version = "0.2.2", features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
@@ -163,11 +165,13 @@ objc2-foundation = { version = "0.2.2", features = [
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
"block2",
"dispatch",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
@@ -179,6 +183,8 @@ objc2-ui-kit = { version = "0.2.2", features = [
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
@@ -208,6 +214,7 @@ windows-sys = { version = "0.52.0", features = [
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -251,6 +258,7 @@ wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code",
"cursor",
"dl-libxcb",
"randr",
"resource_manager",
@@ -263,16 +271,16 @@ xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
redox_syscall = "0.5.7"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.64"
js-sys = "0.3.70"
pin-project = "1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.64", features = [
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
@@ -289,6 +297,7 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlHtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
@@ -336,11 +345,9 @@ wasm-bindgen-test = "0.3"
[[example]]
doc-scrape-examples = true
name = "window"
required-features = ["rwh_06"]
[[example]]
name = "child_window"
required-features = ["rwh_06"]
[workspace]
members = ["dpi"]

View File

@@ -47,201 +47,3 @@ through the implementation work necessary to function on all platforms. When one
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|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** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|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 |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 image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|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** |
### 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 |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 |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804

View File

@@ -2,13 +2,13 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
[![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs
)](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml
[dependencies]
winit = "0.30.4"
winit = "0.30.5"
```
## [Documentation](https://docs.rs/winit)
@@ -66,4 +66,4 @@ same MSRV policy.
### Platform-specific usage
Check out the [`winit::platform`](https://rust-windowing.github.io/winit/winit/platform/index.html) module for platform-specific usage.
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.

View File

@@ -7,6 +7,7 @@
all-features = true
exclude-dev = true
targets = [
{ triple = "aarch64-apple-darwin" },
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
@@ -25,12 +26,12 @@ targets = [
[licenses]
allow = [
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"ISC", # https://tldrlegal.com/license/-isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
]
confidence-threshold = 1.0
private = { ignore = true }
@@ -54,19 +55,6 @@ crate = "android-activity"
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow = [
{ path = "releases/friends.sh", checksum = "f896ccdcb8445d29ed6dd0d9a360f94d4f33af2f1cc9965e7bb38b156c45949d" },
]
crate = "wasm-bindgen"
[[bans.build.bypass]]
allow = [
{ path = "ui-tests/update-all-references.sh", checksum = "8b8dbf31e7ada1314956db7a20ab14b13af3ae246a6295afdc7dc96af8ec3773" },
{ path = "ui-tests/update-references.sh", checksum = "65375c25981646e08e8589449a06be4505b1a2c9e10d35f650be4b1b495dff22" },
]
crate = "wasm-bindgen-macro"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"

View File

@@ -9,3 +9,10 @@ by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.
## `coordinate-systems*`
These files are created by [Mads Marquart](https://github.com/madsmtm) using
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.

View File

@@ -0,0 +1,130 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0" version="24.8.6" pages="2">
<diagram name="desktop" id="3DDum1nDijUk3y7wIDRm">
<mxGraphModel dx="1080" dy="707" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="500" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="cRYnzpdCW-J0f_YpP3mc-1" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#E8E8E8;fontColor=#333333;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="200" y="80" width="480" height="360" as="geometry" />
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-4" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#d5e8d4;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="260" y="340" width="360" height="40" as="geometry" />
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-2" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#dae8fc;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="260" y="140" width="360" height="80" as="geometry" />
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-3" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="200" y="60" width="480" height="20" as="geometry" />
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-5" value="" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#d5e8d4;" parent="1" vertex="1">
<mxGeometry x="260" y="180" width="360" height="180" as="geometry" />
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-6" value="" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-4" target="cRYnzpdCW-J0f_YpP3mc-2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="10" y="310" as="sourcePoint" />
<mxPoint x="60" y="260" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-7" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-4" target="cRYnzpdCW-J0f_YpP3mc-2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="770" y="570" as="sourcePoint" />
<mxPoint x="770" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-8" value="" style="endArrow=none;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;strokeColor=#666666;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" target="cRYnzpdCW-J0f_YpP3mc-5" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="220.00000000000023" y="179.69" as="sourcePoint" />
<mxPoint x="740.0000000000002" y="179.69" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-9" value="&lt;font&gt;outer_position&lt;/font&gt;" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;exitX=0;exitY=0;exitDx=0;exitDy=0;dashed=1;align=right;fontSize=20;fontFamily=monospace;fontColor=#6C8EBF;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#dae8fc;strokeColor=#6C8EBF;endFill=1;startArrow=oval;startFill=1;endSize=6;targetPerimeterSpacing=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-3" edge="1" target="cRYnzpdCW-J0f_YpP3mc-2">
<mxGeometry x="-0.36" y="-24" width="50" height="50" relative="1" as="geometry">
<mxPoint x="80" y="160" as="sourcePoint" />
<mxPoint x="240" y="160" as="targetPoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-10" value="&lt;font&gt;outer_size&lt;/font&gt;" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#6C8EBF;labelBackgroundColor=none;spacingLeft=15;spacingRight=0;spacing=0;exitX=1;exitY=0;exitDx=0;exitDy=0;fillColor=#dae8fc;strokeColor=#6c8ebf;entryX=1;entryY=1;entryDx=0;entryDy=0;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" edge="1" target="cRYnzpdCW-J0f_YpP3mc-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="850" y="170" as="sourcePoint" />
<mxPoint x="760" y="420" as="targetPoint" />
<Array as="points">
<mxPoint x="860" y="140" />
<mxPoint x="860" y="380" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-11" value="&lt;font&gt;surface_size&lt;/font&gt;" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=15;spacingRight=0;spacing=0;entryX=1;entryY=1;entryDx=0;entryDy=0;fillColor=#d5e8d4;strokeColor=#82B366;" parent="1" target="cRYnzpdCW-J0f_YpP3mc-4" edge="1">
<mxGeometry x="0.0526" width="50" height="50" relative="1" as="geometry">
<mxPoint x="600" y="180" as="sourcePoint" />
<mxPoint x="760" y="420" as="targetPoint" />
<Array as="points">
<mxPoint x="700" y="180" />
<mxPoint x="700" y="380" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="cRYnzpdCW-J0f_YpP3mc-12" value="&lt;font&gt;surface_position&lt;/font&gt;" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;dashed=1;align=right;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#d5e8d4;strokeColor=#82b366;exitX=0;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;curved=1;startArrow=oval;startFill=1;endFill=1;" parent="1" source="cRYnzpdCW-J0f_YpP3mc-2" target="cRYnzpdCW-J0f_YpP3mc-5" edge="1">
<mxGeometry y="-50" width="50" height="50" relative="1" as="geometry">
<mxPoint x="140" y="140" as="sourcePoint" />
<mxPoint x="160" y="200" as="targetPoint" />
<Array as="points">
<mxPoint x="250" y="160" />
</Array>
<mxPoint x="-5" y="-22" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram name="mobile" id="D5mAeJSS4Z33KEKjPCBt">
<mxGraphModel dx="1710" dy="1120" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="720" pageHeight="720" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-1" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E8E8E8;fontColor=#333333;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="200" y="40" width="320" height="640" as="geometry" />
</mxCell>
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-2" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="210" y="50" width="300" height="620" as="geometry" />
</mxCell>
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-4" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="210" y="90" width="300" height="540" as="geometry" />
</mxCell>
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-20" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="290" y="640" width="140" height="10" as="geometry" />
</mxCell>
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-3" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#DBDBDB;strokeColor=#666666;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="300" y="50" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="RxwCrVmIsQwV7z5iJ9nY-12" value="&lt;font&gt;surface_size&lt;/font&gt;" style="endArrow=none;html=1;strokeWidth=3;rounded=0;dashed=1;align=left;fontSize=20;fontFamily=monospace;fontColor=#82B366;labelBackgroundColor=none;spacingLeft=15;spacingRight=15;spacing=0;fillColor=#d5e8d4;strokeColor=#82b366;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" source="RxwCrVmIsQwV7z5iJ9nY-2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="50" as="sourcePoint" />
<mxPoint x="510" y="670" as="targetPoint" />
<Array as="points">
<mxPoint x="560" y="50" />
<mxPoint x="560" y="670" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="NrHAzeOh65jb3hkBOxW9-1" value="&lt;div&gt;safe_area.top&lt;/div&gt;" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;align=right;fontSize=20;fontFamily=monospace;fontColor=#D79B00;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#ffe6cc;strokeColor=#d79b00;startArrow=blockThin;startFill=1;endFill=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="180" y="50" as="sourcePoint" />
<mxPoint x="180" y="90" as="targetPoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="NrHAzeOh65jb3hkBOxW9-5" value="&lt;div&gt;safe_area.bottom&lt;/div&gt;" style="endArrow=blockThin;html=1;strokeWidth=3;rounded=0;align=right;fontSize=20;fontFamily=monospace;fontColor=#D79B00;labelBackgroundColor=none;spacingLeft=0;spacingRight=15;spacing=0;fillColor=#ffe6cc;strokeColor=#d79b00;startArrow=blockThin;startFill=1;endFill=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="180" y="670" as="sourcePoint" />
<mxPoint x="180" y="630" as="targetPoint" />
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="720" viewBox="-0.5 -0.5 720 720" class="ge-export-svg-auto"><defs><style><![CDATA[@media (prefers-color-scheme:dark){svg.ge-export-svg-auto svg:not(mjx-container>svg),svg.ge-export-svg-auto:not(mjx-container>svg){filter:invert(100%) hue-rotate(180deg)}}]]></style></defs><g data-cell-id="0"><g data-cell-id="1"><rect x="200" y="40" width="320" height="640" rx="48" ry="48" fill="#e8e8e8" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-1"/><rect x="210" y="50" width="300" height="620" rx="45" ry="45" fill="#d5e8d4" stroke="#82b366" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-2"/><path fill="#ffe6cc" stroke="#d79b00" pointer-events="all" d="M210 90h300v540H210z" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-4"/><rect x="290" y="640" width="140" height="10" rx="1.5" ry="1.5" fill="#dbdbdb" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-20"/><rect x="300" y="50" width="120" height="30" rx="4.5" ry="4.5" fill="#dbdbdb" stroke="#666" pointer-events="all" data-cell-id="RxwCrVmIsQwV7z5iJ9nY-3"/><g data-cell-id="RxwCrVmIsQwV7z5iJ9nY-12"><path d="M510 50h50v620h-50" fill="none" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" stroke-dasharray="9 9" pointer-events="stroke"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-start;width:1px;height:1px;padding-top:360px;margin-left:575px"><div style="box-sizing:border-box;font-size:0;text-align:left" data-drawio-colors="color: #82B366;"><div style="display:inline-block;font-size:20px;font-family:&quot;monospace&quot;;color:#82b366;line-height:1.2;pointer-events:all;white-space:nowrap"><font>surface_size</font></div></div></div></foreignObject><text x="575" y="366" fill="#82B366" font-family="&quot;monospace&quot;" font-size="20">surfa...</text></switch></g><g data-cell-id="NrHAzeOh65jb3hkBOxW9-1"><path d="M180 62.35v15.3" fill="none" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/><path d="m180 53.35 3 9h-6ZM180 86.65l-3-9h6Z" fill="#d79b00" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-end;width:1px;height:1px;padding-top:70px;margin-left:165px"><div style="box-sizing:border-box;font-size:0;text-align:right" data-drawio-colors="color: #D79B00;"><div style="display:inline-block;font-size:20px;font-family:&quot;monospace&quot;;color:#d79b00;line-height:1.2;pointer-events:all;white-space:nowrap"><div>safe_area.top</div></div></div></div></foreignObject><text x="165" y="76" fill="#D79B00" font-family="&quot;monospace&quot;" font-size="20" text-anchor="end">safe_...</text></switch></g><g data-cell-id="NrHAzeOh65jb3hkBOxW9-5"><path d="M180 657.65v-15.3" fill="none" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/><path d="m180 666.65-3-9h6ZM180 633.35l3 9h-6Z" fill="#d79b00" stroke="#d79b00" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/><switch transform="translate(-.5 -.5)"><foreignObject style="overflow:visible;text-align:left" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display:flex;align-items:unsafe center;justify-content:unsafe flex-end;width:1px;height:1px;padding-top:650px;margin-left:165px"><div style="box-sizing:border-box;font-size:0;text-align:right" data-drawio-colors="color: #D79B00;"><div style="display:inline-block;font-size:20px;font-family:&quot;monospace&quot;;color:#d79b00;line-height:1.2;pointer-events:all;white-space:nowrap"><div>safe_area.bottom</div></div></div></div></foreignObject><text x="165" y="656" fill="#D79B00" font-family="&quot;monospace&quot;" font-size="20" text-anchor="end">safe_...</text></switch></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -11,6 +11,8 @@ Unreleased` header.
## Unreleased
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
## 0.1.1
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.

View File

@@ -26,12 +26,13 @@ targets = [
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"x86_64-apple-ios",
"aarch64-apple-ios",
# Android
"aarch64-linux-android",
# Web

View File

@@ -759,6 +759,150 @@ impl<P: Pixel> From<LogicalPosition<P>> for Position {
}
}
/// The logical distance between the edges of two rectangles.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LogicalInsets<P> {
/// The distance to the top edge.
pub top: P,
/// The distance to the left edge.
pub left: P,
/// The distance to the bottom edge.
pub bottom: P,
/// The distance to the right edge.
pub right: P,
}
impl<P> LogicalInsets<P> {
#[inline]
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
Self { top, left, bottom, right }
}
}
impl<P: Pixel> LogicalInsets<P> {
#[inline]
pub fn from_physical<T: Into<PhysicalInsets<X>>, X: Pixel>(
physical: T,
scale_factor: f64,
) -> Self {
physical.into().to_logical(scale_factor)
}
#[inline]
pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<X> {
assert!(validate_scale_factor(scale_factor));
let top = self.top.into() * scale_factor;
let left = self.left.into() * scale_factor;
let bottom = self.bottom.into() * scale_factor;
let right = self.right.into() * scale_factor;
PhysicalInsets::new(top, left, bottom, right).cast()
}
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalInsets<X> {
LogicalInsets {
top: self.top.cast(),
left: self.left.cast(),
bottom: self.bottom.cast(),
right: self.right.cast(),
}
}
}
/// The physical distance between the edges of two rectangles.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PhysicalInsets<P> {
/// The distance to the top edge.
pub top: P,
/// The distance to the left edge.
pub left: P,
/// The distance to the bottom edge.
pub bottom: P,
/// The distance to the right edge.
pub right: P,
}
impl<P> PhysicalInsets<P> {
#[inline]
pub const fn new(top: P, left: P, bottom: P, right: P) -> Self {
Self { top, left, bottom, right }
}
}
impl<P: Pixel> PhysicalInsets<P> {
#[inline]
pub fn from_logical<T: Into<LogicalInsets<X>>, X: Pixel>(
logical: T,
scale_factor: f64,
) -> Self {
logical.into().to_physical(scale_factor)
}
#[inline]
pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalInsets<X> {
assert!(validate_scale_factor(scale_factor));
let top = self.top.into() / scale_factor;
let left = self.left.into() / scale_factor;
let bottom = self.bottom.into() / scale_factor;
let right = self.right.into() / scale_factor;
LogicalInsets::new(top, left, bottom, right).cast()
}
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalInsets<X> {
PhysicalInsets {
top: self.top.cast(),
left: self.left.cast(),
bottom: self.bottom.cast(),
right: self.right.cast(),
}
}
}
/// Insets that are either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Insets {
Physical(PhysicalInsets<u32>),
Logical(LogicalInsets<f64>),
}
impl Insets {
pub fn new<S: Into<Self>>(insets: S) -> Self {
insets.into()
}
pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalInsets<P> {
match *self {
Self::Physical(insets) => insets.to_logical(scale_factor),
Self::Logical(insets) => insets.cast(),
}
}
pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalInsets<P> {
match *self {
Self::Physical(insets) => insets.cast(),
Self::Logical(insets) => insets.to_physical(scale_factor),
}
}
}
impl<P: Pixel> From<PhysicalInsets<P>> for Insets {
#[inline]
fn from(insets: PhysicalInsets<P>) -> Self {
Self::Physical(insets.cast())
}
}
impl<P: Pixel> From<LogicalInsets<P>> for Insets {
#[inline]
fn from(insets: LogicalInsets<P>) -> Self {
Self::Logical(insets.cast())
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;

View File

@@ -8,7 +8,7 @@ fn main() -> Result<(), impl std::error::Error> {
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowId};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
@@ -16,15 +16,15 @@ fn main() -> Result<(), impl std::error::Error> {
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
windows: HashMap<WindowId, Box<dyn Window>>,
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attributes = Window::default_attributes()
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
@@ -44,7 +44,7 @@ fn main() -> Result<(), impl std::error::Error> {
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
WindowEvent::PointerEntered { device_id: _, .. } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
@@ -57,14 +57,14 @@ fn main() -> Result<(), impl std::error::Error> {
..
} => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
fill::fill_window(window.as_ref());
}
},
_ => (),
@@ -72,11 +72,14 @@ fn main() -> Result<(), impl std::error::Error> {
}
}
fn spawn_child_window(parent: &Window, event_loop: &dyn ActiveEventLoop) -> Window {
fn spawn_child_window(
parent: &dyn Window,
event_loop: &dyn ActiveEventLoop,
) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
let mut window_attributes = WindowAttributes::default()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.

View File

@@ -11,7 +11,7 @@ use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowId};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
@@ -52,7 +52,7 @@ struct ControlFlowDemo {
request_redraw: bool,
wait_cancelled: bool,
close_requested: bool,
window: Option<Window>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for ControlFlowDemo {
@@ -66,7 +66,7 @@ impl ApplicationHandler for ControlFlowDemo {
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = Window::default_attributes().with_title(
let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
@@ -114,7 +114,7 @@ impl ApplicationHandler for ControlFlowDemo {
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window);
fill::fill_window(window.as_ref());
},
_ => (),
}

View File

@@ -11,19 +11,19 @@ fn main() -> std::process::ExitCode {
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowId};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[derive(Default)]
struct PumpDemo {
window: Option<Window>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for PumpDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = Window::default_attributes().with_title("A fantastic window!");
let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
@@ -43,7 +43,7 @@ fn main() -> std::process::ExitCode {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(window);
fill::fill_window(window.as_ref());
window.request_redraw();
},
_ => (),

View File

@@ -9,7 +9,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
use winit::window::{Window, WindowId};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
@@ -18,7 +18,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
struct App {
idx: usize,
window_id: Option<WindowId>,
window: Option<Window>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for App {
@@ -29,9 +29,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = Window::default_attributes()
let window_attributes = WindowAttributes::default()
.with_title("Fantastic window number one!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0));
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let window = event_loop.create_window(window_attributes).unwrap();
self.window_id = Some(window.id());
self.window = Some(window);
@@ -65,11 +65,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
CloseRequested",
self.idx
);
fill::cleanup_window(window);
fill::cleanup_window(window.as_ref());
self.window = None;
},
WindowEvent::RedrawRequested => {
fill::fill_window(window);
fill::fill_window(window.as_ref());
},
_ => (),
}

View File

@@ -11,7 +11,7 @@
pub use platform::cleanup_window;
pub use platform::fill_window;
#[cfg(all(feature = "rwh_06", not(any(target_os = "android", target_os = "ios"))))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
@@ -34,18 +34,20 @@ mod platform {
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static Window>>,
context: RefCell<Context<&'static dyn Window>>,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static Window, &'static Window>>,
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
impl GraphicsContext {
fn new(w: &Window) -> Self {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe { mem::transmute::<&'_ Window, &'static Window>(w) })
.expect("Failed to create a softbuffer context"),
Context::new(unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
})
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
@@ -53,24 +55,24 @@ mod platform {
fn create_surface(
&mut self,
window: &Window,
) -> &mut Surface<&'static Window, &'static Window> {
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ Window, &'static Window>(window)
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &Window) {
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
pub fn fill_window(window: &Window) {
pub fn fill_window(window: &dyn Window) {
GC.with(|gc| {
let size = window.inner_size();
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
@@ -94,7 +96,7 @@ mod platform {
}
#[allow(dead_code)]
pub fn cleanup_window(window: &Window) {
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
@@ -104,14 +106,14 @@ mod platform {
}
}
#[cfg(not(all(feature = "rwh_06", not(any(target_os = "android", target_os = "ios")))))]
#[cfg(any(target_os = "android", target_os = "ios"))]
mod platform {
pub fn fill_window(_window: &winit::window::Window) {
pub fn fill_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &winit::window::Window) {
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
}

View File

@@ -3,7 +3,7 @@
use std::collections::HashMap;
use std::error::Error;
use std::fmt::Debug;
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
use std::num::NonZeroU32;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc;
@@ -11,28 +11,31 @@ use std::{fmt, mem};
use ::tracing::{error, info};
use cursor_icon::CursorIcon;
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::ExternalError;
use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
use winit::platform::macos::{
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS,
};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
use winit::platform::wayland::{EventLoopExtWayland, WaylandApplicationHandler};
#[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Window, WindowId,
Theme, Window, WindowAttributes, WindowId,
};
#[path = "util/tracing.rs"]
@@ -47,7 +50,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let mut event_loop = EventLoop::new()?;
let event_loop = EventLoop::new()?;
let (sender, receiver) = mpsc::channel();
// Wire the user event from another thread.
@@ -68,7 +71,6 @@ fn main() -> Result<(), Box<dyn Error>> {
}
let app = Application::new(&event_loop, receiver, sender);
event_loop.register_wayland_callback::<Application>();
Ok(event_loop.run_app(app)?)
}
@@ -78,21 +80,21 @@ struct Application {
receiver: Receiver<Action>,
sender: Sender<Action>,
/// Custom cursors assets.
custom_cursors: Result<Vec<CustomCursor>, ExternalError>,
custom_cursors: Result<Vec<CustomCursor>, RequestError>,
/// Application icon.
icon: Icon,
windows: HashMap<WindowId, WindowState>,
/// Drawing context.
///
/// With OpenGL it could be EGLDisplay.
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
context: Option<Context<DisplayHandle<'static>>>,
}
impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
@@ -121,7 +123,7 @@ impl Application {
Self {
receiver,
sender,
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
context,
custom_cursors,
icon,
@@ -137,7 +139,7 @@ impl Application {
// TODO read-out activation token.
#[allow(unused_mut)]
let mut window_attributes = Window::default_attributes()
let mut window_attributes = WindowAttributes::default()
.with_title("Winit window")
.with_transparent(true)
.with_window_icon(Some(self.icon.clone()));
@@ -149,6 +151,28 @@ impl Application {
window_attributes = window_attributes.with_activation_token(token);
}
#[cfg(x11_platform)]
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
}
#[cfg(macos_platform)]
if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
@@ -218,6 +242,10 @@ impl Application {
Action::ToggleResizable => window.toggle_resizable(),
Action::ToggleDecorations => window.toggle_decorations(),
Action::ToggleFullscreen => window.toggle_fullscreen(),
#[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => {
window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
},
Action::ToggleMaximize => window.toggle_maximize(),
Action::ToggleImeInput => window.toggle_ime(),
Action::Minimize => window.minimize(),
@@ -373,11 +401,6 @@ impl Application {
}
impl ApplicationHandler for Application {
fn as_any(&mut self) -> Option<&mut dyn std::any::Any> {
println!("Called");
Some(self)
}
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
while let Ok(action) = self.receiver.try_recv() {
self.handle_action_from_proxy(event_loop, action)
@@ -396,7 +419,7 @@ impl ApplicationHandler for Application {
};
match event {
WindowEvent::Resized(size) => {
WindowEvent::SurfaceResized(size) => {
window.resize(size);
},
WindowEvent::Focused(focused) => {
@@ -453,20 +476,23 @@ impl ApplicationHandler for Application {
}
}
},
WindowEvent::MouseInput { button, state, .. } => {
WindowEvent::PointerButton { button, state, .. } => {
info!("Pointer button {button:?} {state:?}");
let mods = window.modifiers;
if let Some(action) =
state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
if let Some(action) = state
.is_pressed()
.then(|| Self::process_mouse_binding(button.mouse_button(), &mods))
.flatten()
{
self.handle_action_with_window(event_loop, window_id, action);
}
},
WindowEvent::CursorLeft { .. } => {
info!("Cursor left Window={window_id:?}");
WindowEvent::PointerLeft { .. } => {
info!("Pointer left Window={window_id:?}");
window.cursor_left();
},
WindowEvent::CursorMoved { position, .. } => {
info!("Moved cursor to {position:?}");
WindowEvent::PointerMoved { position, .. } => {
info!("Moved pointer to {position:?}");
window.cursor_moved(position);
},
WindowEvent::ActivationTokenDone { token: _token, .. } => {
@@ -517,11 +543,10 @@ impl ApplicationHandler for Application {
WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. }
| WindowEvent::CursorEntered { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DroppedFile(_)
| WindowEvent::HoveredFile(_)
| WindowEvent::Destroyed
| WindowEvent::Touch(_)
| WindowEvent::Moved(_) => (),
}
}
@@ -529,7 +554,7 @@ impl ApplicationHandler for Application {
fn device_event(
&mut self,
_event_loop: &dyn ActiveEventLoop,
device_id: DeviceId,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
info!("Device {device_id:?} event: {event:?}");
@@ -552,16 +577,27 @@ impl ApplicationHandler for Application {
}
}
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) {
// We must drop the context here.
self.context = None;
}
#[cfg(target_os = "macos")]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
Some(self)
}
}
impl WaylandApplicationHandler for Application {
fn wayland_callback(&mut self) {
println!("Wayland callback!");
#[cfg(target_os = "macos")]
impl ApplicationHandlerExtMacOS for Application {
fn standard_key_binding(
&mut self,
_event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
info!(?window_id, ?action, "macOS standard key binding");
}
}
@@ -572,10 +608,10 @@ struct WindowState {
/// Render surface.
///
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(any(android_platform, ios_platform)))]
surface: Surface<DisplayHandle<'static>, Arc<Window>>,
#[cfg(not(android_platform))]
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window.
window: Arc<Window>,
window: Arc<dyn Window>,
/// The window theme we're drawing with.
theme: Theme,
/// Cursor position over the window.
@@ -603,31 +639,31 @@ struct WindowState {
}
impl WindowState {
fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
let window = Arc::new(window);
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window);
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark);
info!("Theme: {theme:?}");
let named_idx = 0;
window.set_cursor(CURSORS[named_idx]);
window.set_cursor(CURSORS[named_idx].into());
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let size = window.inner_size();
let size = window.surface_size();
let mut state = Self {
#[cfg(macos_platform)]
option_as_alt: window.option_as_alt(),
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
cursor_grab: CursorGrabMode::None,
named_idx,
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
surface,
window,
theme,
@@ -649,7 +685,7 @@ impl WindowState {
self.ime = !self.ime;
self.window.set_ime_allowed(self.ime);
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
}
}
@@ -660,7 +696,7 @@ impl WindowState {
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
self.cursor_position = Some(position);
if self.ime {
self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
}
}
@@ -694,12 +730,12 @@ impl WindowState {
/// Toggle resize increments on a window.
fn toggle_resize_increments(&mut self) {
let new_increments = match self.window.resize_increments() {
let new_increments = match self.window.surface_resize_increments() {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
None => Some(LogicalSize::new(25.0, 25.0).into()),
};
info!("Had increments: {}", new_increments.is_none());
self.window.set_resize_increments(new_increments);
self.window.set_surface_resize_increments(new_increments);
}
/// Toggle fullscreen.
@@ -738,22 +774,22 @@ impl WindowState {
self.window.set_option_as_alt(self.option_as_alt);
}
/// Swap the window dimensions with `request_inner_size`.
/// Swap the window dimensions with `request_surface_size`.
fn swap_dimensions(&mut self) {
let old_inner_size = self.window.inner_size();
let mut inner_size = old_inner_size;
let old_surface_size = self.window.surface_size();
let mut surface_size = old_surface_size;
mem::swap(&mut inner_size.width, &mut inner_size.height);
info!("Requesting resize from {old_inner_size:?} to {inner_size:?}");
mem::swap(&mut surface_size.width, &mut surface_size.height);
info!("Requesting resize from {old_surface_size:?} to {surface_size:?}");
if let Some(new_inner_size) = self.window.request_inner_size(inner_size) {
if old_inner_size == new_inner_size {
if let Some(new_surface_size) = self.window.request_surface_size(surface_size.into()) {
if old_surface_size == new_surface_size {
info!("Inner size change got ignored");
} else {
self.resize(new_inner_size);
self.resize(new_surface_size);
}
} else {
info!("Request inner size is asynchronous");
info!("Requesting surface size is asynchronous");
}
}
@@ -779,7 +815,7 @@ impl WindowState {
) -> Result<(), Box<dyn Error>> {
let cursor = event_loop.create_custom_cursor(url_custom_cursor())?;
self.window.set_cursor(cursor);
self.window.set_cursor(cursor.into());
Ok(())
}
@@ -801,15 +837,15 @@ impl WindowState {
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = event_loop.create_custom_cursor(cursor)?;
self.window.set_cursor(cursor);
self.window.set_cursor(cursor.into());
Ok(())
}
/// Resize the window to the new size.
/// Resize the surface to the new size.
fn resize(&mut self, size: PhysicalSize<u32>) {
info!("Resized to {size:?}");
#[cfg(not(any(android_platform, ios_platform)))]
info!("Surface resized to {size:?}");
#[cfg(not(android_platform))]
{
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
@@ -830,7 +866,7 @@ impl WindowState {
/// Show window menu.
fn show_menu(&self) {
if let Some(position) = self.cursor_position {
self.window.show_window_menu(position);
self.window.show_window_menu(position.into());
}
}
@@ -853,7 +889,7 @@ impl WindowState {
},
};
let win_size = self.window.inner_size();
let win_size = self.window.surface_size();
let border_size = BORDER_SIZE * self.window.scale_factor();
let x_direction = if position.x < border_size {
@@ -902,29 +938,49 @@ impl WindowState {
}
/// Draw the window contents.
#[cfg(not(any(android_platform, ios_platform)))]
#[cfg(not(android_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded {
info!("Skipping drawing occluded window={:?}", self.window.id());
return Ok(());
}
const WHITE: u32 = 0xffffffff;
const DARK_GRAY: u32 = 0xff181818;
let color = match self.theme {
Theme::Light => WHITE,
Theme::Dark => DARK_GRAY,
};
let mut buffer = self.surface.buffer_mut()?;
buffer.fill(color);
// Draw a different color inside the safe area
let surface_size = self.window.surface_size();
let insets = self.window.safe_area();
for y in 0..surface_size.height {
for x in 0..surface_size.width {
let index = y as usize * surface_size.width as usize + x as usize;
if insets.left <= x
&& x <= (surface_size.width - insets.right)
&& insets.top <= y
&& y <= (surface_size.height - insets.bottom)
{
// In safe area
buffer[index] = match self.theme {
Theme::Light => 0xffe8e8e8, // Light gray
Theme::Dark => 0xff525252, // Medium gray
};
} else {
// Outside safe area
buffer[index] = match self.theme {
Theme::Light => 0xffffffff, // White
Theme::Dark => 0xff181818, // Dark gray
};
}
}
}
// Present the buffer
self.window.pre_present_notify();
buffer.present()?;
Ok(())
}
#[cfg(any(android_platform, ios_platform))]
#[cfg(android_platform)]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
info!("Drawing but without rendering...");
Ok(())
@@ -957,6 +1013,8 @@ enum Action {
ToggleDecorations,
ToggleResizable,
ToggleFullscreen,
#[cfg(macos_platform)]
ToggleSimpleFullscreen,
ToggleMaximize,
Minimize,
NextCursor,
@@ -990,6 +1048,8 @@ impl Action {
Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen",
#[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
@@ -1132,6 +1192,8 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
#[cfg(macos_platform)]
Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),

View File

@@ -7,21 +7,21 @@ fn main() -> Result<(), Box<dyn Error>> {
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{Window, WindowId};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
pub struct XEmbedDemo {
parent_window_id: u32,
window: Option<Window>,
window: Option<Box<dyn Window>>,
}
impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = Window::default_attributes()
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
@@ -38,7 +38,7 @@ fn main() -> Result<(), Box<dyn Error>> {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
window.pre_present_notify();
fill::fill_window(window);
fill::fill_window(window.as_ref());
},
_ => (),
}

View File

@@ -1,9 +1,9 @@
//! End user application handling.
use std::any::Any;
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop;
#[cfg(any(docsrs, macos_platform))]
use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId;
/// The handler of the application events.
@@ -43,11 +43,21 @@ pub trait ApplicationHandler {
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a
/// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing
/// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method.
///
/// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart()
///
/// ### Others
///
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
@@ -73,26 +83,24 @@ pub trait ApplicationHandler {
///
/// ### Android
///
/// On Android, the [`can_create_surfaces()`] method is called when a new [`SurfaceView`] has
/// been created. This is expected to closely correlate with the [`onResume`] lifecycle
/// event but there may technically be a discrepancy.
/// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`]
/// (native [`Surface`]) is created which backs the application window. This is expected to
/// closely correlate with the [`onStart`] lifecycle event which typically results in a surface
/// to be created after the app becomes visible.
///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
///
/// Applications that need to run on Android must wait until they have been "resumed" before
/// Applications that need to run on Android must wait until they have received a surface before
/// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`]
/// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must also
/// assume that if they are [suspended], then their render surfaces are invalid and should
/// be dropped.
/// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle
/// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped.
///
/// [suspended]: Self::destroy_surfaces
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces
/// [`destroy_surfaces()`]: Self::destroy_surfaces
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
@@ -198,7 +206,7 @@ pub trait ApplicationHandler {
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: DeviceId,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
let _ = (event_loop, device_id, event);
@@ -237,18 +245,30 @@ pub trait ApplicationHandler {
/// ### Web
///
/// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the
/// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
/// page is being stored 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.
///
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method.
///
/// After this event the application either receives [`resumed()`] again, or [`exiting()`].
///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
///
/// ### Others
///
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`suspended()`]: Self::suspended
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
@@ -261,24 +281,22 @@ pub trait ApplicationHandler {
///
/// ### Android
///
/// On Android, the [`destroy_surfaces()`] method is called when the application's associated
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
/// lifecycle event but there may technically be a discrepancy.
/// On Android, the [`destroy_surfaces()`] method is called when the application's
/// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible.
///
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
///
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// After being [suspended] on Android applications must drop all render surfaces before
/// the event callback completes, which may be re-created when the application is next
/// [resumed].
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
/// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
///
/// [suspended]: Self::destroy_surfaces
/// [resumed]: Self::can_create_surfaces
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
@@ -286,8 +304,8 @@ pub trait ApplicationHandler {
///
/// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported.
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces
/// [`destroy_surfaces()`]: Self::destroy_surfaces
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
@@ -328,22 +346,18 @@ pub trait ApplicationHandler {
let _ = event_loop;
}
/// Get the [`ApplicationHandler`] as [`Any`].
/// The macOS-specific handler.
///
/// This is useful for downcasting to a concrete application type.
/// The return value from this should not change at runtime.
#[cfg(any(docsrs, macos_platform))]
#[inline(always)]
fn as_any(&mut self) -> Option<&mut dyn Any> {
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
None
}
}
#[deny(clippy::missing_trait_methods)]
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
#[inline(always)]
fn as_any(&mut self) -> Option<&mut dyn Any> {
(**self).as_any()
}
#[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -378,7 +392,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: DeviceId,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
@@ -408,15 +422,16 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}
#[deny(clippy::missing_trait_methods)]
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
#[inline(always)]
fn as_any(&mut self) -> Option<&mut dyn Any> {
(**self).as_any()
}
#[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -451,7 +466,7 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: DeviceId,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
@@ -481,4 +496,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}

View File

@@ -43,29 +43,45 @@ changelog entry.
### Added
- Add `ActiveEventLoop::create_proxy()`.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Web, add `ActiveEventLoopExtWeb::is_cursor_lock_raw()` to determine if
`DeviceEvent::MouseMotion` is returning raw data, not OS accelerated, when using
`CursorGrabMode::Locked`.
- On Web, implement `MonitorHandle` and `VideoModeHandle`.
Without prompting the user for permission, only the current monitor is returned. But when
prompting and being granted permission through
`ActiveEventLoop::request_detailed_monitor_permission()`, access to all monitors and their
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
well.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- Add `Touch::finger_id` with a new type `FingerId`.
- On Web and Windows, add `FingerIdExt*::is_primary()`, exposing a way to determine
the primary finger in a multi-touch interaction.
details is available. Handles created with "detailed monitor permissions" can be used in
`Window::set_fullscreen()` as well.
Keep in mind that handles do not auto-upgrade after permissions are granted and have to be
re-created to make full use of this feature.
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
and `Serialize` on many types.
- Add `MonitorHandle::current_video_mode()`.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
to use a larger style of titlebar.
- Add `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
pointer events as part of the pointer event overhaul.
- Add `DeviceId::into_raw()` and `from_raw()`.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- Added `Window::surface_position`, which is the position of the surface inside the window.
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
### Changed
- Change `ActiveEventLoop` to be a trait.
- Change `Window` to be a trait.
- `ActiveEventLoop::create_window` now returns `Box<dyn Window>`.
- `ApplicationHandler` now uses `dyn ActiveEventLoop`.
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.73`.
@@ -78,14 +94,14 @@ changelog entry.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop.
- On X11, implement smooth resizing through the sync extension API.
- `ApplicationHandler::create|destroy_surfaces()` was split off from
- `ApplicationHandler::can_create|destroy_surfaces()` was split off from
`ApplicationHandler::resumed/suspended()`.
`ApplicationHandler::can_create_surfaces()` should, for portability reasons
to Android, be the only place to create render surfaces.
`ApplicationHandler::resumed/suspended()` are now only emitted by iOS and Web
and now signify actually resuming/suspending the application.
`ApplicationHandler::resumed/suspended()` are now only emitted by iOS, Web
and Android, and now signify actually resuming/suspending the application.
- Rename `platform::web::*ExtWebSys` to `*ExtWeb`.
- Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and
`EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly,
@@ -97,6 +113,58 @@ changelog entry.
- Changed how `ModifiersState` is serialized by Serde.
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
- `MonitorHandle::position()` now returns an `Option`.
- On iOS and macOS, remove custom application delegates. You are now allowed to override the
application delegate yourself.
- On iOS, no longer act as-if the application successfully open all URLs. Override
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
- On X11, remove our dependency on libXcursor. (#3749)
- Renamed the following APIs to make it clearer that the sizes apply to the underlying surface:
- `WindowEvent::Resized` to `SurfaceResized`.
- `InnerSizeWriter` to `SurfaceSizeWriter`.
- `WindowAttributes.inner_size` to `surface_size`.
- `WindowAttributes.min_inner_size` to `min_surface_size`.
- `WindowAttributes.max_inner_size` to `max_surface_size`.
- `WindowAttributes.resize_increments` to `surface_resize_increments`.
- `WindowAttributes::with_inner_size` to `with_surface_size`.
- `WindowAttributes::with_min_inner_size` to `with_min_surface_size`.
- `WindowAttributes::with_max_inner_size` to `with_max_surface_size`.
- `WindowAttributes::with_resize_increments` to `with_surface_resize_increments`.
- `Window::inner_size` to `surface_size`.
- `Window::request_inner_size` to `request_surface_size`.
- `Window::set_min_inner_size` to `set_min_surface_size`.
- `Window::set_max_inner_size` to `set_max_surface_size`.
To migrate, you can probably just replace all instances of `inner_size` with `surface_size` in your codebase.
- Every event carrying a `DeviceId` now uses `Option<DeviceId>` instead. A `None` value signifies that the
device can't be uniquely identified.
- Pointer `WindowEvent`s were overhauled. The new events can handle any type of pointer, serving as
a single pointer input source. Now your application can handle any pointer type without having to
explicitly handle e.g. `Touch`:
- Rename `CursorMoved` to `PointerMoved`.
- Rename `CursorEntered` to `PointerEntered`.
- Rename `CursorLeft` to `PointerLeft`.
- Rename `MouseInput` to `PointerButton`.
- Add `primary` to every `PointerEvent` as a way to identify discard non-primary pointers in a
multi-touch interaction.
- Add `position` to every `PointerEvent`.
- `PointerMoved` is **not sent** after `PointerEntered` anymore.
- Remove `Touch`, which is folded into the `Pointer*` events.
- New `PointerKind` added to `PointerEntered` and `PointerLeft`, signifying which pointer type is
the source of this event.
- New `PointerSource` added to `PointerMoved`, similar to `PointerKind` but holding additional
data.
- New `ButtonSource` added to `PointerButton`, similar to `PointerKind` but holding pointer type
specific buttons. Use `ButtonSource::mouse_button()` to easily normalize any pointer button
type to a generic mouse button.
- New `FingerId` added to `PointerKind::Touch` and `PointerSource::Touch` able to uniquely
identify a finger in a multi-touch interaction. Replaces the old `Touch::id`.
- In the same spirit rename `DeviceEvent::MouseMotion` to `PointerMotion`.
- Remove `Force::Calibrated::altitude_angle`.
- On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`.
- On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation.
- On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3`
### Removed
@@ -119,14 +187,32 @@ changelog entry.
v0.5 support. v0.6 remains in place and is enabled by default.
- Remove `DeviceEvent::Added` and `DeviceEvent::Removed`.
- Remove `DeviceEvent::Motion` and `WindowEvent::AxisMotion`.
- Remove `Touch::id` in favor of `Touch::finger_id`.
- Remove `MonitorHandle::size()` and `refresh_rate_millihertz()` in favor of
`MonitorHandle::current_video_mode()`.
- On Android, remove all `MonitorHandle` support instead of emitting false data.
- Remove `impl From<u64> for WindowId` and `impl From<WindowId> for u64`. Replaced with
`WindowId::into_raw()` and `from_raw()`.
- Remove `dummy()` from `WindowId` and `DeviceId`.
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`.
- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
### Fixed
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
- On macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.

View File

@@ -1,3 +1,18 @@
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4
### Changed

View File

@@ -50,7 +50,7 @@ impl From<CustomCursor> for Cursor {
/// ```no_run
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &Window) {
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit::window::CustomCursor;
///
/// let w = 10;
@@ -67,7 +67,7 @@ impl From<CustomCursor> for Cursor {
/// };
///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
/// window.set_cursor(custom_cursor.clone());
/// window.set_cursor(custom_cursor.clone().into());
/// }
/// # }
/// ```

View File

@@ -1,45 +1,40 @@
use std::{error, fmt};
use std::error::Error;
use std::fmt::{self, Display};
use crate::platform_impl;
// 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),
}
/// The error type for when the requested operation is not supported by the backend.
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NotSupportedError {
_marker: (),
}
/// The error type for when the OS cannot perform the requested operation.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: platform_impl::OsError,
}
/// A general error that may occur while running the Winit event loop
/// A general error that may occur while running or creating
/// the event loop.
#[derive(Debug)]
#[non_exhaustive]
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-created.
RecreationAttempt,
/// Application has exit with an error status.
ExitFailure(i32),
/// Got unspecified OS-specific error during the request.
Os(OsError),
/// Creating the event loop with the requested configuration is not supported.
NotSupported(NotSupportedError),
}
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
Self::Os(err) => err.fmt(f),
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f),
}
}
}
impl Error for EventLoopError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self {
err.source()
} else {
None
}
}
}
impl From<OsError> for EventLoopError {
@@ -48,18 +43,102 @@ impl From<OsError> for EventLoopError {
}
}
impl NotSupportedError {
#[inline]
#[allow(dead_code)]
pub(crate) fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
impl From<NotSupportedError> for EventLoopError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}
/// A general error that may occur during a request to the windowing system.
#[derive(Debug)]
#[non_exhaustive]
pub enum RequestError {
/// The request is not supported.
NotSupported(NotSupportedError),
/// The request was ignored by the operating system.
Ignored,
/// Got unspecified OS specific error during the request.
Os(OsError),
}
impl Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotSupported(err) => err.fmt(f),
Self::Ignored => write!(f, "The request was ignored"),
Self::Os(err) => err.fmt(f),
}
}
}
impl Error for RequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self {
err.source()
} else {
None
}
}
}
impl From<NotSupportedError> for RequestError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}
impl From<OsError> for RequestError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}
/// The requested operation is not supported.
#[derive(Debug)]
pub struct NotSupportedError {
/// The reason why a certain operation is not supported.
reason: &'static str,
}
impl NotSupportedError {
pub(crate) fn new(reason: &'static str) -> Self {
Self { reason }
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Operation is not supported: {}", self.reason)
}
}
impl Error for NotSupportedError {}
/// Unclassified error from the OS.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: Box<dyn Error + Send + Sync + 'static>,
}
impl OsError {
#[allow(dead_code)]
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
OsError { line, file, error }
pub(crate) fn new(
line: u32,
file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
) -> Self {
Self { line, file, error: error.into() }
}
}
impl Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
impl Error for OsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.error.as_ref())
}
}
@@ -69,64 +148,3 @@ macro_rules! os_error {
crate::error::OsError::new(line!(), file!(), $error)
}};
}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
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),
}
}
}
impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
}
}
impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.pad("the requested operation is not supported by Winit")
}
}
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
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)]
#[allow(clippy::redundant_clone)]
mod tests {
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

@@ -46,7 +46,7 @@ use smol_str::SmolStr;
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::ExternalError;
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
@@ -63,51 +63,51 @@ use crate::window::{ActivationToken, Theme, WindowId};
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events`] for details.
/// See [`ApplicationHandler::new_events()`] for details.
///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
NewEvents(StartCause),
/// See [`ApplicationHandler::window_event`] for details.
/// See [`ApplicationHandler::window_event()`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event`] for details.
/// See [`ApplicationHandler::device_event()`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: DeviceId, event: DeviceEvent },
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
/// See [`ApplicationHandler::suspended`] for details.
/// See [`ApplicationHandler::suspended()`] for details.
///
/// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended,
/// See [`ApplicationHandler::can_create_surfaces`] for details.
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
///
/// [`ApplicationHandler::can_create_surfaces`]: crate::application::ApplicationHandler::can_create_surfaces
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
CreateSurfaces,
/// See [`ApplicationHandler::resumed`] for details.
/// See [`ApplicationHandler::resumed()`] for details.
///
/// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
Resumed,
/// See [`ApplicationHandler::about_to_wait`] for details.
/// See [`ApplicationHandler::about_to_wait()`] for details.
///
/// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait,
/// See [`ApplicationHandler::exiting`] for details.
/// See [`ApplicationHandler::exiting()`] for details.
///
/// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
LoopExiting,
/// See [`ApplicationHandler::memory_warning`] for details.
/// See [`ApplicationHandler::memory_warning()`] for details.
///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
MemoryWarning,
/// User requested a wake up.
@@ -148,10 +148,21 @@ pub enum WindowEvent {
/// [`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>),
/// The size of the window's surface has changed.
///
/// Contains the new dimensions of the surface (can also be retrieved with
/// [`Window::surface_size`]).
///
/// This event will not necessarily be emitted upon window creation, query
/// [`Window::surface_size`] if you need to determine the surface's initial size.
///
/// [`Window::surface_size`]: crate::window::Window::surface_size
SurfaceResized(PhysicalSize<u32>),
/// The position of the window has changed. Contains the window's new position.
/// The position of the window has changed.
///
/// Contains the window's new position in desktop coordinates (can also be retrieved with
/// [`Window::outer_position`]).
///
/// ## Platform-specific
///
@@ -164,18 +175,23 @@ pub enum WindowEvent {
/// The window has been destroyed.
Destroyed,
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
DroppedFile(PathBuf),
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
///
/// The support for this is known to be incomplete, see [#720] for more
/// information.
///
/// [#720]: https://github.com/rust-windowing/winit/issues/720
DroppedFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
@@ -185,6 +201,9 @@ pub enum WindowEvent {
/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
///
/// Windows are unfocused upon creation, but will usually be focused by the system soon
/// afterwards.
Focused(bool),
/// An event from the keyboard has been received.
@@ -193,8 +212,9 @@ pub enum WindowEvent {
/// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down,
/// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key
/// events which are not marked as `is_synthetic`.
/// - **iOS:** Unsupported.
KeyboardInput {
device_id: DeviceId,
device_id: Option<DeviceId>,
event: KeyEvent,
/// If `true`, the event was generated synthetically by winit
@@ -221,52 +241,113 @@ pub enum WindowEvent {
/// - **iOS / Android / Web / Orbital:** Unsupported.
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,
/// The pointer has moved on the window.
PointerMoved {
device_id: Option<DeviceId>,
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range
/// of this data is limited by the display area and it may have been transformed by
/// the OS to implement effects such as cursor acceleration, it should not be used
/// to implement non-cursor-like interactions such as 3D camera control.
/// (x,y) coordinates in pixels relative to the top-left corner of the window. Because the
/// range of this data is limited by the display area and it may have been
/// transformed by the OS to implement effects such as pointer acceleration, it
/// should not be used to implement non-pointer-like interactions such as 3D camera
/// control. For that, consider [`DeviceEvent::PointerMotion`].
///
/// ## 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
position: PhysicalPosition<f64>,
/// Indicates whether the event is created by a primary pointer.
///
/// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
/// interaction, or an unknown pointer source.
primary: bool,
source: PointerSource,
},
/// 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 pointer has entered the window.
PointerEntered {
device_id: Option<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 },
/// The position of the pointer when it entered the window.
///
/// ## Platform-specific
///
/// - **Orbital: Always emits `(0., 0.)`.
/// - **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
position: PhysicalPosition<f64>,
/// Indicates whether the event is created by a primary pointer.
///
/// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
/// interaction, or an unknown pointer source.
primary: bool,
kind: PointerKind,
},
/// The pointer has left the window.
PointerLeft {
device_id: Option<DeviceId>,
/// The position of the pointer when it left the window. The position reported can be
/// outside the bounds of the window.
///
/// ## Platform-specific
///
/// - **Orbital/Windows:** Always emits [`None`].
/// - **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
position: Option<PhysicalPosition<f64>>,
/// Indicates whether the event is created by a primary pointer.
///
/// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
/// interaction, or an unknown pointer source.
primary: bool,
kind: PointerKind,
},
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase },
MouseWheel { device_id: Option<DeviceId>, delta: MouseScrollDelta, phase: TouchPhase },
/// An mouse button press has been received.
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton },
PointerButton {
device_id: Option<DeviceId>,
state: ElementState,
/// The position of the pointer when the button was pressed.
///
/// ## Platform-specific
///
/// - **Orbital: Always emits `(0., 0.)`.
/// - **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
position: PhysicalPosition<f64>,
/// Indicates whether the event is created by a primary pointer.
///
/// A pointer is considered primary when it's a mouse, the first finger in a multi-touch
/// interaction, or an unknown pointer source.
primary: bool,
button: ButtonSource,
},
/// Two-finger pinch gesture, often used for magnification.
///
@@ -275,7 +356,7 @@ pub enum WindowEvent {
/// - Only available on **macOS** and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
PinchGesture {
device_id: DeviceId,
device_id: Option<DeviceId>,
/// Positive values indicate magnification (zooming in) and negative
/// values indicate shrinking (zooming out).
///
@@ -291,7 +372,7 @@ pub enum WindowEvent {
/// - Only available on **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
PanGesture {
device_id: DeviceId,
device_id: Option<DeviceId>,
/// Change in pixels of pan gesture from last update.
delta: PhysicalPosition<f32>,
phase: TouchPhase,
@@ -315,7 +396,7 @@ pub enum WindowEvent {
///
/// - Only available on **macOS 10.8** and later, and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
DoubleTapGesture { device_id: DeviceId },
DoubleTapGesture { device_id: Option<DeviceId> },
/// Two-finger rotation gesture.
///
@@ -327,7 +408,7 @@ pub enum WindowEvent {
/// - Only available on **macOS** and **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture {
device_id: DeviceId,
device_id: Option<DeviceId>,
/// change in rotation in degrees
delta: f32,
phase: TouchPhase,
@@ -335,22 +416,18 @@ pub enum WindowEvent {
/// Touchpad pressure event.
///
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the
/// touchpad is being pressed) and stage (integer representing the click level).
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
/// Touch event has been received
///
/// ## 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),
/// - **macOS**: Only supported on Apple forcetouch-capable macbooks.
/// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported.
TouchpadPressure {
device_id: Option<DeviceId>,
/// Value between 0 and 1 representing how hard the touchpad is being
/// pressed.
pressure: f32,
/// Represents the click level.
stage: i64,
},
/// The window's scale factor has changed.
///
@@ -360,16 +437,21 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor.
///
/// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the
/// To update the window size, use the provided [`SurfaceSizeWriter`] handle. By default, the
/// window is resized to the value suggested by the OS, but it can be changed to any value.
///
/// This event will not necessarily be emitted upon window creation, query
/// [`Window::scale_factor`] if you need to determine the window's initial scale factor.
///
/// For more information about DPI in general, see the [`dpi`] crate.
///
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
ScaleFactorChanged {
scale_factor: f64,
/// Handle to update inner size during scale changes.
/// Handle to update surface size during scale changes.
///
/// See [`InnerSizeWriter`] docs for more details.
inner_size_writer: InnerSizeWriter,
/// See [`SurfaceSizeWriter`] docs for more details.
surface_size_writer: SurfaceSizeWriter,
},
/// The system window theme has changed.
@@ -415,16 +497,141 @@ pub enum WindowEvent {
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// This gets triggered in a few scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// resizing the window, or changing [the safe area]).
/// - 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.
///
/// [the safe area]: crate::window::Window::safe_area
RedrawRequested,
}
/// Represents the kind type of a pointer event.
///
/// ## Platform-specific
///
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
/// system.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PointerKind {
Mouse,
/// See [`PointerSource::Touch`] for more details.
///
/// ## Platform-specific
///
/// **macOS:** Unsupported.
Touch(FingerId),
Unknown,
}
/// Represents the pointer type and its data for a pointer event.
///
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
/// system.
#[derive(Clone, Debug, PartialEq)]
pub enum PointerSource {
Mouse,
/// Represents a touch event.
///
/// Every time the user touches the screen, a [`WindowEvent::PointerEntered`] and a
/// [`WindowEvent::PointerButton`] with [`ElementState::Pressed`] event with an unique
/// identifier for the finger is emitted. When a finger is lifted, a
/// [`WindowEvent::PointerButton`] with [`ElementState::Released`] and a
/// [`WindowEvent::PointerLeft`] event is generated with the same [`FingerId`].
///
/// After a [`WindowEvent::PointerEntered`] event has been emitted, there may be zero or more
/// [`WindowEvent::PointerMoved`] events when the finger is moved or the touch pressure
/// changes.
///
/// A [`WindowEvent::PointerLeft`] without a [`WindowEvent::PointerButton`] with
/// [`ElementState::Released`] event is emitted when the system has canceled tracking this
/// touch, such as when the window loses focus, or on mobile devices if the user moves the
/// device against their face.
///
/// The [`FingerId`] may be reused by the system after a [`WindowEvent::PointerLeft`] event.
/// The user should assume that a new [`WindowEvent::PointerEntered`] event received with the
/// same ID has nothing to do with the old finger and is a new finger.
///
/// ## Platform-specific
///
/// **macOS:** Unsupported.
Touch {
finger_id: FingerId,
/// Describes how hard the screen was pressed. May be [`None`] if the hardware does not
/// support pressure sensitivity.
///
/// ## Platform-specific
///
/// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
/// - **Android:** 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).#[derive(Debug, Clone, Copy, PartialEq)]
/// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
force: Option<Force>,
},
Unknown,
}
impl From<PointerSource> for PointerKind {
fn from(source: PointerSource) -> Self {
match source {
PointerSource::Mouse => Self::Mouse,
PointerSource::Touch { finger_id, .. } => Self::Touch(finger_id),
PointerSource::Unknown => Self::Unknown,
}
}
}
/// Represents the pointer type of a [`WindowEvent::PointerButton`].
///
/// **Wayland/X11:** [`Unknown`](Self::Unknown) device types are converted to known variants by the
/// system.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ButtonSource {
Mouse(MouseButton),
/// See [`PointerSource::Touch`] for more details.
///
/// ## Platform-specific
///
/// **macOS:** Unsupported.
Touch {
finger_id: FingerId,
force: Option<Force>,
},
Unknown(u16),
}
impl ButtonSource {
/// Convert any [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no
/// special handling in an application, this method can be used to handle it like any generic
/// mouse input.
pub fn mouse_button(self) -> MouseButton {
match self {
ButtonSource::Mouse(mouse) => mouse,
ButtonSource::Touch { .. } => MouseButton::Left,
ButtonSource::Unknown(button) => match button {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
3 => MouseButton::Back,
4 => MouseButton::Forward,
_ => MouseButton::Other(button),
},
}
}
}
impl From<MouseButton> for ButtonSource {
fn from(mouse: MouseButton) -> Self {
Self::Mouse(mouse)
}
}
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a
@@ -432,24 +639,23 @@ pub enum WindowEvent {
/// on-screen cursor and keyboard focus) or physical. Virtual devices typically aggregate inputs
/// from multiple physical devices.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl Default for DeviceId {
fn default() -> Self {
Self::dummy()
}
}
pub struct DeviceId(i64);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
/// Convert the [`DeviceId`] into the underlying integer.
///
/// # Notes
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> i64 {
self.0
}
/// Construct a [`DeviceId`] from the underlying integer.
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
pub const fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
/// This should only be called with integers returned from [`DeviceId::into_raw`].
#[allow(dead_code)]
pub(crate) const fn from_raw(id: i64) -> Self {
Self(id)
}
}
@@ -458,18 +664,23 @@ impl DeviceId {
/// Whenever a touch event is received it contains a `FingerId` which uniquely identifies the finger
/// used for the current interaction.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId(pub(crate) platform_impl::FingerId);
pub struct FingerId(pub(crate) usize);
impl FingerId {
/// Returns a dummy id, useful for unit testing.
/// Convert the [`FingerId`] into the underlying integer.
///
/// # Notes
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> usize {
self.0
}
/// Construct a [`FingerId`] from the underlying integer.
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `FingerId`.
pub const fn dummy() -> Self {
FingerId(platform_impl::FingerId::dummy())
/// This should only be called with integers returned from [`FingerId::into_raw`].
#[allow(dead_code)]
pub(crate) const fn from_raw(id: usize) -> Self {
Self(id)
}
}
@@ -478,7 +689,7 @@ impl FingerId {
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
/// device and window events. Because window events typically arise from virtual devices
/// (corresponding to GUI cursors and keyboard focus) the device IDs may not match.
/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -486,7 +697,7 @@ pub enum DeviceEvent {
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with
/// [`WindowEvent::CursorMoved`].
/// [`WindowEvent::PointerMoved`].
///
/// ## Platform-specific
///
@@ -503,7 +714,7 @@ pub enum DeviceEvent {
///
#[rustfmt::skip]
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
MouseMotion {
PointerMotion {
/// (x, y) change in position in unspecified units.
///
/// Different devices may use different units.
@@ -832,50 +1043,6 @@ pub enum TouchPhase {
Cancelled,
}
/// Represents a touch event
///
/// Every time the user touches the screen, a new [`TouchPhase::Started`] event with an unique
/// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`]
/// event is generated with the same finger id.
///
/// After a `Started` event has been emitted, there may be zero or more `Move`
/// events when the finger is moved or the touch pressure changes.
///
/// The finger id may be reused by the system after an `Ended` event. The user
/// should assume that a new `Started` event received with the same id has nothing
/// to do with the old finger and is a new finger.
///
/// A [`TouchPhase::Cancelled`] event is emitted when the system has canceled tracking this
/// touch, such as when the window loses focus, or on iOS if the user moves the
/// device against their face.
///
/// ## 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,
pub phase: TouchPhase,
pub location: PhysicalPosition<f64>,
/// Describes how hard the screen was pressed. May be `None` if the platform
/// does not support pressure sensitivity.
///
/// ## Platform-specific
///
/// - 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 finger_id: FingerId,
}
/// Describes the force of a touch event
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -896,12 +1063,6 @@ pub enum Force {
/// The value of this field is sufficiently high to provide a wide
/// dynamic range for values of the `force` field.
max_possible_force: f64,
/// The altitude (in radians) of the stylus.
///
/// A value of 0 radians indicates that the stylus is parallel to the
/// surface. The value of this property is Pi/2 when the stylus is
/// perpendicular to the surface.
altitude_angle: Option<f64>,
},
/// If the platform reports the force as normalized, we have no way of
/// knowing how much pressure 1.0 corresponds to we know it's the maximum
@@ -918,13 +1079,7 @@ impl Force {
/// consistent across devices.
pub fn normalized(&self) -> f64 {
match self {
Force::Calibrated { force, max_possible_force, altitude_angle } => {
let force = match altitude_angle {
Some(altitude_angle) => force / altitude_angle.sin(),
None => *force,
};
force / max_possible_force
},
Force::Calibrated { force, max_possible_force } => force / max_possible_force,
Force::Normalized(force) => *force,
}
}
@@ -995,40 +1150,39 @@ pub enum MouseScrollDelta {
PixelDelta(PhysicalPosition<f64>),
}
/// Handle to synchronously change the size of the window from the
/// [`WindowEvent`].
/// Handle to synchronously change the size of the window from the [`WindowEvent`].
#[derive(Debug, Clone)]
pub struct InnerSizeWriter {
pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
pub struct SurfaceSizeWriter {
pub(crate) new_surface_size: Weak<Mutex<PhysicalSize<u32>>>,
}
impl InnerSizeWriter {
impl SurfaceSizeWriter {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_inner_size }
pub(crate) fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_surface_size }
}
/// Try to request inner size which will be set synchronously on the window.
pub fn request_inner_size(
/// Try to request surface size which will be set synchronously on the window.
pub fn request_surface_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;
new_surface_size: PhysicalSize<u32>,
) -> Result<(), RequestError> {
if let Some(inner) = self.new_surface_size.upgrade() {
*inner.lock().unwrap() = new_surface_size;
Ok(())
} else {
Err(ExternalError::Ignored)
Err(RequestError::Ignored)
}
}
}
impl PartialEq for InnerSizeWriter {
impl PartialEq for SurfaceSizeWriter {
fn eq(&self, other: &Self) -> bool {
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
self.new_surface_size.as_ptr() == other.new_surface_size.as_ptr()
}
}
impl Eq for InnerSizeWriter {}
impl Eq for SurfaceSizeWriter {}
#[cfg(test)]
mod tests {
@@ -1041,18 +1195,18 @@ mod tests {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = event::DeviceId::dummy();
let fid = event::FingerId::dummy();
let fid = event::FingerId::from_raw(0);
#[allow(deprecated)]
{
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::window::WindowId;
// Mainline events.
let wid = WindowId::dummy();
let wid = WindowId::from_raw(0);
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
@@ -1066,49 +1220,69 @@ mod tests {
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(SurfaceResized((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(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did });
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: did,
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(MouseInput {
device_id: did,
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
button: event::MouseButton::Other(0),
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: did,
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: did });
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: did,
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: did,
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: did, pressure: 0.0, stage: 0 });
with_window_event(Touch(event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
}));
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
@@ -1118,9 +1292,9 @@ mod tests {
use event::DeviceEvent::*;
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: did, event: dev_ev });
|dev_ev| x(event::Event::DeviceEvent { device_id: None, event: dev_ev });
with_device_event(MouseMotion { delta: (0.0, 0.0).into() });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
@@ -1143,15 +1317,10 @@ mod tests {
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 };
let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
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),
};
let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5 };
assert_eq!(force3.normalized(), 2.0);
}
@@ -1159,33 +1328,22 @@ mod tests {
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event| {
let _ = format!("{:?}", event);
let _ = format!("{event:?}");
});
let _ = event::StartCause::Init.clone();
let did = crate::event::DeviceId::dummy().clone();
let fid = crate::event::FingerId::dummy().clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
let fid = crate::event::FingerId::from_raw(0).clone();
HashSet::new().insert(fid);
let mut set = [fid, fid, fid];
set.sort_unstable();
let mut set2 = BTreeSet::new();
set2.insert(did);
set2.insert(did);
set2.insert(fid);
set2.insert(fid);
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(),
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
}
.clone();
let _ =
event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None }
.clone();
let _ = event::Force::Calibrated { force: 0.0, max_possible_force: 0.0 }.clone();
}
}

View File

@@ -8,22 +8,24 @@
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::any::Any;
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError, OsError};
use crate::error::{EventLoopError, RequestError};
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::utils::AsAny;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
@@ -72,7 +74,7 @@ impl EventLoopBuilder {
/// 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
/// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event
/// loop on any thread.
///
@@ -268,15 +270,14 @@ impl EventLoop {
pub fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, ExternalError> {
) -> Result<CustomCursor, RequestError> {
self.event_loop.window_target().create_custom_cursor(custom_cursor)
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
impl HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
}
}
@@ -308,7 +309,7 @@ impl AsRawFd for EventLoop {
}
}
pub trait ActiveEventLoop {
pub trait ActiveEventLoop: AsAny {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy;
@@ -321,7 +322,10 @@ pub trait ActiveEventLoop {
///
/// - **Web:** The window is created but not inserted into the Web page automatically. Please
/// see the Web platform module for more information.
fn create_window(&self, window_attributes: WindowAttributes) -> Result<Window, OsError>;
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn Window>, RequestError>;
/// Create custom cursor.
///
@@ -331,7 +335,7 @@ pub trait ActiveEventLoop {
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, ExternalError>;
) -> Result<CustomCursor, RequestError>;
/// Returns the list of all the monitors available on the system.
///
@@ -403,19 +407,19 @@ pub trait ActiveEventLoop {
/// See the [`OwnedDisplayHandle`] type for more information.
fn owned_display_handle(&self) -> OwnedDisplayHandle;
/// Get the [`ActiveEventLoop`] as [`Any`].
///
/// This is useful for downcasting to a concrete event loop type.
fn as_any(&self) -> &dyn Any;
/// Get the raw-window-handle handle.
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle;
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
}
impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.rwh_06_handle().display_handle()
}
}
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
@@ -425,36 +429,55 @@ pub trait ActiveEventLoop {
///
/// - A zero-sized type that is likely optimized out.
/// - A reference-counted pointer to the underlying type.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone)]
pub struct OwnedDisplayHandle {
#[cfg_attr(not(feature = "rwh_06"), allow(dead_code))]
pub(crate) platform: platform_impl::OwnedDisplayHandle,
pub(crate) handle: Arc<dyn HasDisplayHandle>,
}
impl OwnedDisplayHandle {
pub(crate) fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
}
impl fmt::Debug for OwnedDisplayHandle {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
#[inline]
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.platform.raw_display_handle_rwh_06()?;
// SAFETY: The underlying display handle should be safe.
let handle = unsafe { rwh_06::DisplayHandle::borrow_raw(raw) };
Ok(handle)
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self.display_handle(), other.display_handle()) {
(Ok(lhs), Ok(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
pub(crate) trait EventLoopProxyProvider: Send + Sync {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
}
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone)]
pub struct EventLoopProxy {
pub(crate) event_loop_proxy: platform_impl::EventLoopProxy,
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
}
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopProxy").finish_non_exhaustive()
}
}
impl EventLoopProxy {
@@ -474,13 +497,11 @@ impl EventLoopProxy {
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.event_loop_proxy.wake_up();
self.proxy.wake_up();
}
}
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ActiveEventLoop").finish_non_exhaustive()
pub(crate) fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
}

View File

@@ -1,5 +1,59 @@
//! Winit is a cross-platform window creation and event loop management library.
//!
//! # Usage
//!
//! `winit` can be added to `Cargo.toml` as a dependency. It can be added via `cargo add`.
//!
//! ```bash
//! $ cargo add winit
//! ```
//!
//! To only enable the X11 backend on Free Unix[^unix] systems, disable default features
//! and enable the `x11` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features x11
//! ```
//!
//! To only enable the Wayland backend on Free Unix systems, disable default features
//! and enable the `wayland` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features wayland
//! ```
//!
//! These features have no effect on systems that are not Free Unix.
//!
//! ## Dependencies
//!
//! Dependencies on non-system libraries is managed through Cargo. For the X11
//! backend, the following Ubuntu packages or their equivalents must[^must] be installed.
//!
//! - `libx11-dev`
//! - `libxcb1-dev`
//! - `libxi-dev`
//! - `libxcbcommon-dev`
//! - `libxcbcommon-x11-dev`
//!
//! For the Wayland backend, the following Ubuntu packages or their equivalents
//! must be installed.
//!
//! - `libwayland-dev`
//! - `libxcbcommon-dev`
//! - `libfontconfig` (only with `sctk-adwaita` feature)
//! - `freetype` (only with `sctk-adwaita` feature)
//!
//! The "dev" packages are only needed for building binaries that use `winit`. On
//! deployed system the non-`dev` equivalents need to be installed.
//!
//! The other backends (Windows, macOS, etc) do not have any dependencies on system libraries
//! that don't already come with the operating system. However, note that the Windows backend
//! only supports Windows 10 and above, and the macOS backend only supports macOS
//! 10.14 and above.
//!
//! [^unix]: Unix systems outside of Android and Apple, like Linux or FreeBSD.
//! [^must]: This is not a "must" when the "dlopen" features are enabled
//!
//! # Building windows
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
@@ -7,7 +61,12 @@
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//! let event_loop = EventLoop::new().unwrap();
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! // ...
//! }
//! ```
//!
//! Then you create a [`Window`] with [`create_window`].
@@ -44,16 +103,16 @@
//! use winit::application::ApplicationHandler;
//! use winit::event::WindowEvent;
//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
//! use winit::window::{Window, WindowId};
//! use winit::window::{Window, WindowId, WindowAttributes};
//!
//! #[derive(Default)]
//! struct App {
//! window: Option<Window>,
//! window: Option<Box<dyn Window>>,
//! }
//!
//! impl ApplicationHandler for App {
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
//! self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! }
//!
//! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
@@ -83,19 +142,22 @@
//! }
//! }
//!
//! let event_loop = EventLoop::new().unwrap();
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//!
//! // 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::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.
//! event_loop.set_control_flow(ControlFlow::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);
//!
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! }
//! ```
//!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
@@ -115,6 +177,45 @@
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
//! the window visible only once you're ready to render into it.
//!
//! There is another important concept you need to know about when drawing: the "safe area". This
//! can be accessed with [`Window::safe_area`], and describes a rectangle in the surface that is not
//! obscured by notches, the status bar, and so on. You should be drawing your background and
//! non-important content on the entire surface, but restrict important content (such as
//! interactable UIs, text, etc.) to only being drawn inside the safe area.
//!
//! [`Window::safe_area`]: crate::window::Window::safe_area
//!
//! # Coordinate systems
//!
//! Windowing systems use many different coordinate systems, and this is reflected in Winit as well;
//! there are "desktop coordinates", which is the coordinates of a window or monitor relative to the
//! desktop at large, "window coordinates" which is the coordinates of the surface, relative to the
//! window, and finally "surface coordinates", which is the coordinates relative to the drawn
//! surface. All of these coordinates are relative to the top-left corner of their respective
//! origin.
//!
//! Most of the functionality in Winit works with surface coordinates, so usually you only need to
//! concern yourself with those. In case you need to convert to some other coordinate system, Winit
//! provides [`Window::surface_position`] and [`Window::surface_size`] to describe the surface's
//! location in window coordinates, and Winit provides [`Window::outer_position`] and
//! [`Window::outer_size`] to describe the window's location in desktop coordinates. Using these
//! methods, you should be able to convert a position in one coordinate system to another.
//!
//! An overview of how these four methods fit together can be seen in the image below:
#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-desktop.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
//! On mobile, the situation is usually a bit different; because of the smaller screen space,
//! windows usually fill the whole screen at a time, and as such there is _rarely_ a difference
//! between these three coordinate systems, although you should still strive to handle this, as
//! they're still relevant in more niche area such as Mac Catalyst, or multi-tasking on tablets.
//!
//! This is illustrated in the image below, along with the safe area since it's often relevant on
//! mobile.
#![doc = concat!("\n\n", include_str!("../docs/res/coordinate-systems-mobile.svg"), "\n\n")] // Rustfmt removes \n, adding them like this works around that.
//! [`Window::surface_position`]: crate::window::Window::surface_position
//! [`Window::surface_size`]: crate::window::Window::surface_size
//! [`Window::outer_position`]: crate::window::Window::outer_position
//! [`Window::outer_size`]: crate::window::Window::outer_size
//!
//! # UI scaling
//!
//! UI scaling is important, go read the docs for the [`dpi`] crate for an
@@ -147,6 +248,62 @@
//! See the [`platform`] module for documentation on platform-specific cargo
//! features.
//!
//! # Platform/Architecture Support
//!
//! Platform support on `winit` has two tiers: Tier 1 and Tier 2.
//!
//! - Tier 1 is **guaranteed to work**. Targets in this tier are actively tested both in CI and by
//! maintainers.
//! - Tier 2 is **guaranteed to build**. Code compilation is tested in CI, but deeper testing is not
//! done.
//!
//! Please open an issue if you would like to add a Tier 2 target, or if you would
//! like a Tier 2 target moved to Tier 1.
//!
//! ## Tier 1 Targets
//!
//! |Target Name |Target Triple |APIs |
//! |-------------------------------|------------------------------------|---------------|
//! |32-Bit x86 Windows with MSVC |`i686-pc-windows-msvc` |Win32 |
//! |64-Bit x86 Windows with MSVC |`x86_64-pc-windows-msvc` |Win32 |
//! |32-Bit x86 Windows with glibc |`i686-pc-windows-gnu` |Win32 |
//! |64-Bit x86 Windows with glibc |`x86_64-pc-windows-gnu` |Win32 |
//! |32-Bit x86 Linux with glibc |`i686-unknown-linux-gnu` |X11, Wayland |
//! |64-Bit x86 Linux with glibc |`x86_64-unknown-linux-gnu` |X11, Wayland |
//! |64-Bit ARM Android |`aarch64-linux-android` |Android |
//! |64-Bit x86 Redox OS |`x86_64-unknown-redox` |Orbital |
//! |32-Bit x86 Redox OS |`i686-unknown-redox` |Orbital |
//! |64-Bit ARM Redox OS |`aarch64-unknown-redox` |Orbital |
//! |64-bit x64 macOS |`x86_64-apple-darwin` |AppKit |
//! |64-bit ARM macOS |`aarch64-apple-darwin` |AppKit |
//! |32-bit Wasm Web browser |`wasm32-unknown-unknown` |`wasm-bindgen` |
//!
//! ## Tier 2 Targets
//!
//! |Target Name |Target Triple |APIs |
//! |------------------------------------|------------------------------------|---------------|
//! |64-Bit ARM Windows with MSVC |`aarch64-pc-windows-msvc` |Win32 |
//! |32-Bit x86 Windows 7 with MSVC |`i686-win7-windows-msvc` |Win32 |
//! |64-Bit x86 Windows 7 with MSVC |`x86_64-win7-windows-msvc` |Win32 |
//! |64-bit x86 Linux with Musl |`x86_64-unknown-linux-musl` |X11, Wayland |
//! |64-bit x86 Linux with 32-bit glibc |`x86_64-unknown-linux-gnux32` |X11, Wayland |
//! |64-bit x86 Android |`x86_64-linux-android` |Android |
//! |64-bit x64 iOS |`x86_64-apple-ios` |UIKit |
//! |64-bit ARM iOS |`aarch64-apple-ios` |UIKit |
//! |64-bit ARM Mac Catalyst |`aarch64-apple-ios-macabi` |UIKit |
//! |32-bit x86 Android |`i686-linux-android` |Android |
//! |64-bit x86 FreeBSD |`x86_64-unknown-freebsd` |X11, Wayland |
//! |64-bit x86 NetBSD |`x86_64-unknown-netbsd` |X11 |
//! |32-bit x86 Linux with Musl |`i686-unknown-linux-musl` |X11, Wayland |
//! |64-bit RISC-V Linux with glibc |`riscv64gc-unknown-linux-gnu` |X11, Wayland |
//! |64-bit ARM Linux with glibc |`aarch64-unknown-linux-gnu` |X11, Wayland |
//! |64-bit ARM Linux with Musl |`aarch64-unknown-linux-musl` |X11, Wayland |
//! |64-bit PowerPC Linux with glibc |`powerpc64le-unknown-linux-gnu` |X11, Wayland |
//! |32-Bit ARM Linux with glibc |`armv5te-unknown-linux-gnueabi` |X11, Wayland |
//! |64-Bit Linux on IBM Supercomputers |`s390x-unknown-linux-gnu` |X11, Wayland |
//! |32-bit ARM Android |`arm-linux-androideabi` |Android |
//! |64-bit SPARC Linux with glibc |`sparc64-unknown-linux-gnu` |X11, Wayland |
//!
//! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new
//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app
@@ -174,11 +331,11 @@
// doc
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)]
#![warn(clippy::uninlined_format_args)]
// Re-export DPI types so that users don't have to put it in Cargo.toml.
#[doc(inline)]
pub use dpi;
#[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle;
pub mod application;

View File

@@ -1,18 +1,12 @@
//! Types useful for interacting with a user's monitors.
//!
//! If you want to get basic information about a monitor, you can use the
//! [`MonitorHandle`] type. This is retrieved from one of the following
//! methods, which return an iterator of [`MonitorHandle`]:
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use std::num::{NonZeroU16, NonZeroU32};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl;
/// Describes a fullscreen video mode of a monitor.
/// A handle to a fullscreen video mode of a specific monitor.
///
/// Can be acquired with [`MonitorHandle::video_modes`].
/// This can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
@@ -47,9 +41,9 @@ impl Ord for VideoModeHandle {
impl VideoModeHandle {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::inner_size()`] instead.
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::inner_size()`]: crate::window::Window::inner_size
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
@@ -92,7 +86,15 @@ impl std::fmt::Display for VideoModeHandle {
/// Handle to a monitor.
///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
/// Allows you to retrieve basic information and metadata about a monitor.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
///
/// ## Platform-specific
///
@@ -141,8 +143,11 @@ impl MonitorHandle {
self.inner.name()
}
/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
/// Returns the top-left corner position of the monitor in desktop coordinates.
///
/// This position is in the same coordinate system as [`Window::outer_position`].
///
/// [`Window::outer_position`]: crate::window::Window::outer_position
///
/// ## Platform-specific
///

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.4",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -99,17 +99,19 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for Window {
impl WindowExtAndroid for dyn Window + '_ {
fn content_rect(&self) -> Rect {
self.window.content_rect()
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.content_rect()
}
fn config(&self) -> ConfigurationRef {
self.window.config()
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.config()
}
}
impl ActiveEventLoopExtAndroid for &dyn ActiveEventLoop {
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp {
let event_loop =
self.as_any().downcast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();

View File

@@ -1,51 +1,79 @@
//! # iOS / UIKit
//!
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
//! iOS 9.3.
//! Winit has [the same iOS version requirements as `rustc`][rustc-ios-version], although it's
//! frequently only tested on newer iOS versions.
//!
//! iOS's main `UIApplicationMain` does some init work that's required by all
//! UI-related code (see issue [#1705]). It is best to create your windows
//! inside `Event::Resumed`.
//! [rustc-ios-version]: https://doc.rust-lang.org/rustc/platform-support/apple-ios.html#os-version
//!
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
//! ## Running on Mac Catalyst
//!
//! ## Building app
//! Mac Catalyst allows running applications using UIKit on macOS, which can be very useful for
//! testing. See [`rustc`'s documentation on Mac Catalyst][rustc-mac-catalyst] for details on how to
//! use these targets. To use these with Winit, you'll need to bundle your application before
//! running it, otherwise UIKit will exit with an error.
//!
//! To build ios app you will need rustc built for this targets:
//! To run e.g. the `window` example in the Winit repository, you can use [`cargo-bundle`] as
//! follows:
//!
//! - armv7-apple-ios
//! - armv7s-apple-ios
//! - i386-apple-ios
//! - aarch64-apple-ios
//! - x86_64-apple-ios
//!
//! Then
//!
//! ```
//! cargo build --target=...
//! ```
//! The simplest way to integrate your app into xcode environment is to build it
//! as a static library. Wrap your main function and export it.
//!
//! ```rust, ignore
//! #[no_mangle]
//! pub extern fn start_winit_app() {
//! start_inner()
//! }
//!
//! fn start_inner() {
//! ...
//! }
//! ```console
//! $ cargo +nightly bundle --format=ios --target=aarch64-apple-ios-macabi --example=window
//! $ ./target/aarch64-apple-ios-macabi/debug/examples/bundle/ios/winit.app/window
//! ```
//!
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
//! [rustc-mac-catalyst]: https://doc.rust-lang.org/rustc/platform-support/apple-ios-macabi.html
//! [`cargo-bundle`]: https://github.com/burtonageo/cargo-bundle
//!
//! ```ignore
//! void start_winit_app();
//! ## Introduction to building an app
//!
//! Building and running your application in the iOS simulator, or on a real device, is a bit more
//! complicated than Mac Catalyst - fundamentally, you must use Xcode, since the binary needs to be
//! bundled, signed, notarized and uploaded to the device (there is [an open source work-in-progress
//! on re-implementing parts of this][apple-platform-rs], but the user-story around it is not yet
//! clear).
//!
//! This means that you're left with effectively two options: Use a tool that manages the Xcode
//! configuration for you, or use Xcode directly. [`cargo-dinghy`] and [`cargo-mobile2`] are notable
//! projects in the ecosystem that attempt the former, and [`cargo-xcode`] is an excellent project
//! that attempts the latter. We will also attempt to describe here how you would go about using
//! Xcode directly:
//!
//! First off, you'll need the correct Rust targets, see [`rustc`'s documentation on iOS][rustc-ios]
//! for details. Nowadays, the correct targets are usually `aarch64-apple-ios-sim` for the
//! simulator, and `aarch64-apple-ios` for the actual device.
//!
//! Next, create a new Xcode project using the "App" template. The exact configuration does not
//! really matter, as we're going to delete most of it, since it's tailored for Objective-C and/or
//! Swift, and Rust/Winit is neither. Specifically, we need to delete:
//! - Everything relating to storyboards (unless you want to use e.g. a launch screen). This
//! includes the relevant keys in `Info.plist`.
//! - All the generated C header, Objective-C and/or Swift files.
//!
//! Now that we have a fairly clean slate that we can build upon, you can add a "run script" build
//! phase to your Xcode target, which will get invoked instead of the "compile sources" and "link
//! binary" steps. The basic script should look something like:
//!
//! ```sh
//! # Build desired targets based on `ARCHS` environment variable
//! cargo build --target=aarch64-apple-ios --target=armv7s-apple-ios
//! # Merge these with `lipo`, and place the result in "$TARGET_BUILD_DIR/$EXECUTABLE_PATH", which
//! # is understood by Xcode
//! lipo "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" target/aarch64-apple-ios/debug/my_app target/armv7s-apple-ios/debug/my_app
//! ```
//!
//! Use start_winit_app inside your xcode's main function.
//! Note that this is very much the overall idea; the script needs to be much more involved to
//! properly deal with different target architectures, invoking `lipo` when needed, incremental
//! rebuild change detection, and so on. `cargo-xcode` has a script [here][cargo-xcode-script] that
//! handles most of this complexity, you might be able to build upon that.
//!
//! Apologies that we're not able to provide you with more than this; work is in-progress on
//! improving the situation, but it's slow-going.
//!
//! [apple-platform-rs]: https://github.com/indygreg/apple-platform-rs
//! [`cargo-dinghy`]: https://github.com/sonos/dinghy
//! [`cargo-mobile2`]: https://github.com/tauri-apps/cargo-mobile2
//! [`cargo-xcode`]: https://crates.io/crates/cargo-xcode
//! [rustc-ios]: https://doc.rust-lang.org/rustc/platform-support/apple-ios.html
//! [cargo-xcode-script]: https://gitlab.com/kornelski/cargo-xcode/-/blob/main/src/xcodebuild.sh
//!
//! ## App lifecycle and events
//!
@@ -63,6 +91,16 @@
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
//!
//! ## Custom `UIApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to access some of the more niche stuff that [the application
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
//! register an application delegate, so you can set up a custom one in a nib file instead.
//!
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
use std::os::raw::c_void;
@@ -171,42 +209,49 @@ pub trait WindowExtIOS {
fn recognize_rotation_gesture(&self, should_recognize: bool);
}
impl WindowExtIOS for Window {
impl WindowExtIOS for dyn Window + '_ {
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor));
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations));
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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.maybe_queue_on_main(move |w| {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
#[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
#[inline]
@@ -216,7 +261,8 @@ impl WindowExtIOS for Window {
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
self.window.maybe_queue_on_main(move |w| {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| {
w.recognize_pan_gesture(
should_recognize,
minimum_number_of_touches,
@@ -227,12 +273,14 @@ impl WindowExtIOS for Window {
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
}
#[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
}
}

View File

@@ -1,27 +1,84 @@
//! # macOS / AppKit
//!
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
//! itself), and is regularly tested on macOS 10.14.
//! Winit has [the same macOS version requirements as `rustc`][rustc-macos-version], and is tested
//! once in a while on as low as macOS 10.14.
//!
//! A lot of functionality expects the application to be ready before you
//! start doing anything; this includes creating windows, fetching monitors,
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
//! [rustc-macos-version]: https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version
//!
//! If you encounter problems, you should try doing your initialization inside
//! `Event::Resumed`.
//! ## Custom `NSApplicationDelegate`
//!
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
//! increase the API surface by quite a lot.
//!
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
//!
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information).
#![cfg_attr(target_os = "macos", doc = "```")]
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! declare_class!(
//! struct AppDelegate;
//!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {}
//!
//! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[method(application:openURLs:)]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example.
//! println!("open urls: {application:?}, {urls:?}");
//! }
//! }
//! );
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! }
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let event_loop = EventLoop::new()?;
//!
//! let mtm = MainThreadMarker::new().unwrap();
//! let delegate = AppDelegate::new(mtm);
//! // Important: Call `sharedApplication` after `EventLoop::new`,
//! // doing it before is not yet supported.
//! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//!
//! // event_loop.run_app(&mut my_app);
//! Ok(())
//! }
//! ```
use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
use crate::window::{Window, WindowAttributes, WindowId};
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
@@ -35,6 +92,9 @@ pub trait WindowExtMacOS {
/// This is how fullscreen used to work on macOS in versions before Lion.
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
///
/// Make sure you only draw your important content inside the safe area so that it does not
/// overlap with the notch on newer devices, see [`Window::safe_area`] for details.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
/// Returns whether or not the window has shadow.
@@ -94,77 +154,128 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games.
fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
fn is_borderless_game(&self) -> bool;
/// Makes the titlebar bigger, effectively adding more space around the
/// window controls if the titlebar is invisible.
fn set_unified_titlebar(&self, unified_titlebar: bool);
/// Getter for the [`WindowExtMacOS::set_unified_titlebar`].
fn unified_titlebar(&self) -> bool;
}
impl WindowExtMacOS for Window {
impl WindowExtMacOS for dyn Window + '_ {
#[inline]
fn simple_fullscreen(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.simple_fullscreen())
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
fn has_shadow(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.has_shadow())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.has_shadow())
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
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())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
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())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.select_next_tab());
}
#[inline]
fn select_previous_tab(&self) {
self.window.maybe_queue_on_main(|w| w.select_previous_tab())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_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())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.num_tabs())
}
#[inline]
fn is_document_edited(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_document_edited())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_document_edited())
}
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.maybe_wait_on_main(|w| w.option_as_alt())
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game())
}
#[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
}
#[inline]
fn unified_titlebar(&self) -> bool {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar())
}
}
@@ -217,6 +328,10 @@ pub trait WindowAttributesExtMacOS {
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
fn with_borderless_game(self, borderless_game: bool) -> Self;
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -285,23 +400,38 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.option_as_alt = option_as_alt;
self
}
#[inline]
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.platform_specific.borderless_game = borderless_game;
self
}
#[inline]
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
}
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application.
/// Sets the activation policy for the application. If used, this will override
/// any relevant settings provided in the package manifest.
/// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
/// the application from running as an "agent", even if LSUIElement is set to true.
///
/// It is set to [`ActivationPolicy::Regular`] by default.
/// If unused, the Winit will honor the package manifest.
///
/// # Example
///
/// Set the activation policy to "accessory".
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// use winit::event_loop::EventLoop;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
///
/// let mut builder = EventLoopBuilder::new();
/// let mut builder = EventLoop::builder();
/// #[cfg(target_os = "macos")]
/// builder.with_activation_policy(ActivationPolicy::Accessory);
/// # if false { // We can't test this part
@@ -319,11 +449,11 @@ pub trait EventLoopBuilderExtMacOS {
/// Disable creating a default menubar.
///
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// use winit::event_loop::EventLoop;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
///
/// let mut builder = EventLoopBuilder::new();
/// let mut builder = EventLoop::builder();
/// #[cfg(target_os = "macos")]
/// builder.with_default_menu(false);
/// # if false { // We can't test this part
@@ -342,7 +472,7 @@ pub trait EventLoopBuilderExtMacOS {
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self.platform_specific.activation_policy = Some(activation_policy);
self
}
@@ -396,7 +526,7 @@ pub trait ActiveEventLoopExtMacOS {
fn allows_automatic_window_tabbing(&self) -> bool;
}
impl ActiveEventLoopExtMacOS for &dyn ActiveEventLoop {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) {
let event_loop = self
.as_any()
@@ -449,3 +579,52 @@ pub enum OptionAsAlt {
#[default]
None,
}
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
/// The system interpreted a keypress as a standard key binding command.
///
/// Examples include inserting tabs and newlines, or moving the insertion point, see
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
/// editing related.
///
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
///
/// The `action` parameter contains the string representation of the selector. Examples include
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
///
/// # Example
///
/// ```ignore
/// impl ApplicationHandlerExtMacOS for App {
/// fn standard_key_binding(
/// &mut self,
/// event_loop: &dyn ActiveEventLoop,
/// window_id: WindowId,
/// action: &str,
/// ) {
/// match action {
/// "moveBackward:" => self.cursor.position -= 1,
/// "moveForward:" => self.cursor.position += 1,
/// _ => {} // Ignore other actions
/// }
/// }
/// }
/// ```
///
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
#[doc(alias = "doCommandBySelector:")]
fn standard_key_binding(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
let _ = event_loop;
let _ = window_id;
let _ = action;
}
}

View File

@@ -21,6 +21,7 @@ pub mod windows;
#[cfg(any(x11_platform, docsrs))]
pub mod x11;
#[allow(unused_imports)]
#[cfg(any(
windows_platform,
macos_platform,

View File

@@ -23,7 +23,7 @@
use std::env;
use crate::error::NotSupportedError;
use crate::error::{NotSupportedError, RequestError};
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
#[cfg(wayland_platform)]
use crate::platform::wayland::ActiveEventLoopExtWayland;
@@ -46,7 +46,7 @@ pub trait WindowExtStartupNotify {
/// Request a new activation token.
///
/// The token will be delivered inside
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError>;
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError>;
}
pub trait WindowAttributesExtStartupNotify {
@@ -57,7 +57,7 @@ pub trait WindowAttributesExtStartupNotify {
fn with_activation_token(self, token: ActivationToken) -> Self;
}
impl EventLoopExtStartupNotify for &dyn ActiveEventLoop {
impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
fn read_token_from_env(&self) -> Option<ActivationToken> {
#[cfg(x11_platform)]
let _is_wayland = false;
@@ -72,9 +72,22 @@ impl EventLoopExtStartupNotify for &dyn ActiveEventLoop {
}
}
impl WindowExtStartupNotify for Window {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
self.window.request_activation_token()
impl WindowExtStartupNotify for dyn Window + '_ {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
#[cfg(wayland_platform)]
if let Some(window) = self.as_any().downcast_ref::<crate::platform_impl::wayland::Window>()
{
return window.request_activation_token();
}
#[cfg(x11_platform)]
if let Some(window) =
self.as_any().downcast_ref::<crate::platform_impl::x11::window::Window>()
{
return window.request_activation_token();
}
Err(NotSupportedError::new("startup notify is not supported").into())
}
}

View File

@@ -13,11 +13,10 @@
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
pub use crate::window::Theme;
use crate::window::{Window, WindowAttributes};
use crate::window::{Window as CoreWindow, WindowAttributes};
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland {
@@ -25,11 +24,7 @@ pub trait ActiveEventLoopExtWayland {
fn is_wayland(&self) -> bool;
}
pub trait WaylandApplicationHandler: ApplicationHandler + 'static {
fn wayland_callback(&mut self);
}
impl ActiveEventLoopExtWayland for &dyn ActiveEventLoop {
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ {
#[inline]
fn is_wayland(&self) -> bool {
self.as_any().downcast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
@@ -40,8 +35,6 @@ impl ActiveEventLoopExtWayland for &dyn ActiveEventLoop {
pub trait EventLoopExtWayland {
/// True if the [`EventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
fn register_wayland_callback<T: WaylandApplicationHandler>(&mut self);
}
impl EventLoopExtWayland for EventLoop {
@@ -49,22 +42,6 @@ impl EventLoopExtWayland for EventLoop {
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
}
fn register_wayland_callback<T: WaylandApplicationHandler>(&mut self) {
let event_loop = match &self.event_loop {
crate::platform_impl::EventLoop::Wayland(event_loop) => &event_loop.active_event_loop,
#[cfg(x11_platform)]
crate::platform_impl::EventLoop::X(_) => return,
};
event_loop.wayland_callback.set(Some(|app: &mut dyn ApplicationHandler| {
app.as_any()
.expect("as_any_mut is not implemented")
.downcast_mut::<T>()
.unwrap()
.wayland_callback()
}));
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
@@ -94,9 +71,11 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder {
}
/// Additional methods on [`Window`] that are specific to Wayland.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {}
impl WindowExtWayland for Window {}
impl WindowExtWayland for dyn CoreWindow + '_ {}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {

View File

@@ -27,19 +27,18 @@
//! - [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding)
//!
//! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::SurfaceResized`] and [`Window::(set_)surface_size()`]
//! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and
//! [`WindowEvent::Touch`].
//! - [`WindowEvent::PointerMoved`], [`WindowEvent::PointerEntered`] and
//! [`WindowEvent::PointerLeft`].
//! - [`Window::set_outer_position()`]
//!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
//! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size
//! [`WindowEvent::SurfaceResized`]: crate::event::WindowEvent::SurfaceResized
//! [`Window::(set_)surface_size()`]: crate::window::Window::surface_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
//! [`WindowEvent::PointerMoved`]: crate::event::WindowEvent::PointerMoved
//! [`WindowEvent::PointerEntered`]: crate::event::WindowEvent::PointerEntered
//! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
use std::cell::Ref;
@@ -58,7 +57,6 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::error::NotSupportedError;
use crate::event::FingerId;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::monitor::MonitorHandle;
use crate::platform_impl::PlatformCustomCursorSource;
@@ -82,7 +80,7 @@ pub trait WindowExtWeb {
/// Returns [`true`] if calling `event.preventDefault()` is enabled.
///
/// See [`Window::set_prevent_default()`] for more details.
/// See [`WindowExtWeb::set_prevent_default()`] for more details.
fn prevent_default(&self) -> bool;
/// Sets whether `event.preventDefault()` should be called on events on the
@@ -104,22 +102,34 @@ pub trait WindowExtWeb {
fn is_cursor_lock_raw(&self) -> bool;
}
impl WindowExtWeb for Window {
impl WindowExtWeb for dyn Window + '_ {
#[inline]
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> {
self.window.canvas()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.canvas()
}
fn prevent_default(&self) -> bool {
self.window.prevent_default()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.prevent_default()
}
fn set_prevent_default(&self, prevent_default: bool) {
self.window.set_prevent_default(prevent_default)
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.set_prevent_default(prevent_default)
}
fn is_cursor_lock_raw(&self) -> bool {
self.window.is_cursor_lock_raw()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.is_cursor_lock_raw()
}
}
@@ -136,7 +146,7 @@ pub trait WindowAttributesExtWeb {
/// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects.
///
/// See [`Window::set_prevent_default()`] for more details.
/// See [`WindowExtWeb::set_prevent_default()`] for more details.
///
/// Enabled by default.
fn with_prevent_default(self, prevent_default: bool) -> Self;
@@ -347,7 +357,7 @@ pub trait ActiveEventLoopExtWeb {
fn has_detailed_monitor_permission(&self) -> bool;
}
impl ActiveEventLoopExtWeb for &dyn ActiveEventLoop {
impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
let event_loop = self
@@ -699,10 +709,9 @@ pub struct OrientationData {
pub orientation: Orientation,
/// [`true`] if the [`orientation`](Self::orientation) is flipped upside down.
pub flipped: bool,
/// [`true`] if the [`Orientation`] is the most natural one for the screen regardless of being
/// flipped. Computer monitors are commonly naturally landscape mode, while mobile phones
/// are commonly naturally portrait mode.
pub natural: bool,
/// The most natural orientation for the screen. Computer monitors are commonly naturally
/// landscape mode, while mobile phones are commonly naturally portrait mode.
pub natural: Orientation,
}
/// Screen orientation.
@@ -726,14 +735,14 @@ pub enum OrientationLock {
/// User is locked to landscape mode.
Landscape {
/// - [`None`]: User is locked to both upright or upside down landscape mode.
/// - [`false`]: User is locked to upright landscape mode.
/// - [`true`]: User is locked to upright landscape mode.
/// - [`false`]: User is locked to upside down landscape mode.
flipped: Option<bool>,
},
/// User is locked to portrait mode.
Portrait {
/// - [`None`]: User is locked to both upright or upside down portrait mode.
/// - [`false`]: User is locked to upright portrait mode.
/// - [`true`]: User is locked to upright portrait mode.
/// - [`false`]: User is locked to upside down portrait mode.
flipped: Option<bool>,
},
@@ -770,16 +779,3 @@ impl Display for OrientationLockError {
}
impl Error for OrientationLockError {}
/// Additional methods on [`FingerId`] that are specific to Web.
pub trait FingerIdExtWeb {
/// Indicates if the finger represents the first contact in a multi-touch interaction.
#[allow(clippy::wrong_self_convention)]
fn is_primary(self) -> bool;
}
impl FingerIdExtWeb for FingerId {
fn is_primary(self) -> bool {
self.0.is_primary()
}
}

View File

@@ -4,13 +4,16 @@
//! tested regularly.
use std::borrow::Borrow;
use std::ffi::c_void;
use std::ops::Deref;
use std::path::Path;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(windows_platform)]
use windows_sys::Win32::Foundation::HANDLE;
use crate::dpi::PhysicalSize;
use crate::event::{DeviceId, FingerId};
use crate::event::DeviceId;
use crate::event_loop::EventLoopBuilder;
use crate::monitor::MonitorHandle;
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
@@ -115,50 +118,24 @@ pub enum CornerPreference {
///
/// See [`WindowBorrowExtWindows::any_thread`] for more information.
#[derive(Clone, Debug)]
pub struct AnyThread<W>(W);
pub struct AnyThread<W: Window>(W);
impl<W: Borrow<Window>> AnyThread<W> {
impl<W: Window> AnyThread<W> {
/// Get a reference to the inner window.
#[inline]
pub fn get_ref(&self) -> &Window {
self.0.borrow()
}
/// Get a reference to the inner object.
#[inline]
pub fn inner(&self) -> &W {
pub fn get_ref(&self) -> &dyn Window {
&self.0
}
/// Unwrap and get the inner window.
#[inline]
pub fn into_inner(self) -> W {
self.0
}
}
impl<W: Borrow<Window>> AsRef<Window> for AnyThread<W> {
fn as_ref(&self) -> &Window {
self.get_ref()
}
}
impl<W: Borrow<Window>> Borrow<Window> for AnyThread<W> {
fn borrow(&self) -> &Window {
self.get_ref()
}
}
impl<W: Borrow<Window>> std::ops::Deref for AnyThread<W> {
type Target = Window;
impl<W: Window> Deref for AnyThread<W> {
type Target = W;
fn deref(&self) -> &Self::Target {
self.get_ref()
&self.0
}
}
#[cfg(feature = "rwh_06")]
impl<W: Borrow<Window>> rwh_06::HasWindowHandle for AnyThread<W> {
impl<W: Window> rwh_06::HasWindowHandle for AnyThread<W> {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
// SAFETY: The top level user has asserted this is only used safely.
unsafe { self.get_ref().window_handle_any_thread() }
@@ -341,7 +318,7 @@ pub trait WindowExtWindows {
///
/// ```no_run
/// # use winit::window::Window;
/// # fn scope(window: Window) {
/// # fn scope(window: Box<dyn Window>) {
/// use std::thread;
///
/// use winit::platform::windows::WindowExtWindows;
@@ -359,41 +336,46 @@ pub trait WindowExtWindows {
/// });
/// # }
/// ```
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
}
impl WindowExtWindows for Window {
impl WindowExtWindows for dyn Window + '_ {
#[inline]
fn set_enable(&self, enabled: bool) {
self.window.set_enable(enabled)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_enable(enabled)
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
self.window.set_taskbar_icon(taskbar_icon)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_skip_taskbar(skip)
}
#[inline]
fn set_undecorated_shadow(&self, shadow: bool) {
self.window.set_undecorated_shadow(shadow)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_undecorated_shadow(shadow)
}
#[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
self.window.set_system_backdrop(backdrop_type)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_system_backdrop(backdrop_type)
}
#[inline]
fn set_border_color(&self, color: Option<Color>) {
self.window.set_border_color(color.unwrap_or(Color::NONE))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_border_color(color.unwrap_or(Color::NONE))
}
#[inline]
@@ -401,25 +383,28 @@ impl WindowExtWindows for Window {
// The windows docs don't mention NONE as a valid options but it works in practice and is
// useful to circumvent the Windows option "Show accent color on title bars and
// window borders"
self.window.set_title_background_color(color.unwrap_or(Color::NONE))
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_background_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_text_color(&self, color: Color) {
self.window.set_title_text_color(color)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_text_color(color)
}
#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference)
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_corner_preference(preference)
}
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
unsafe {
let handle = self.window.rwh_06_no_thread_check()?;
let handle = window.rwh_06_no_thread_check()?;
// SAFETY: The handle is valid in this context.
Ok(rwh_06::WindowHandle::borrow_raw(handle))
@@ -430,7 +415,7 @@ impl WindowExtWindows for Window {
/// Additional methods for anything that dereference to [`Window`].
///
/// [`Window`]: crate::window::Window
pub trait WindowBorrowExtWindows: Borrow<Window> + Sized {
pub trait WindowBorrowExtWindows: Borrow<dyn Window> + Sized {
/// Create an object that allows accessing the inner window handle in a thread-unsafe way.
///
/// It is possible to call [`window_handle_any_thread`] to get around Windows's thread
@@ -447,22 +432,17 @@ pub trait WindowBorrowExtWindows: Borrow<Window> + Sized {
/// Win32 APIs.
///
/// [`Window`]: crate::window::Window
#[cfg_attr(
feature = "rwh_06",
doc = "[`HasWindowHandle`]: rwh_06::HasWindowHandle",
doc = "[`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread"
)]
#[cfg_attr(
not(feature = "rwh_06"),
doc = "[`HasWindowHandle`]: #only-available-with-rwh_06",
doc = "[`window_handle_any_thread`]: #only-available-with-rwh_06"
)]
unsafe fn any_thread(self) -> AnyThread<Self> {
/// [`HasWindowHandle`]: rwh_06::HasWindowHandle
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
unsafe fn any_thread(self) -> AnyThread<Self>
where
Self: Window,
{
AnyThread(self)
}
}
impl<W: Borrow<Window> + Sized> WindowBorrowExtWindows for W {}
impl<W: Borrow<dyn Window> + Sized> WindowBorrowExtWindows for W {}
/// Additional methods on `WindowAttributes` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
@@ -667,24 +647,15 @@ pub trait DeviceIdExtWindows {
fn persistent_identifier(&self) -> Option<String>;
}
#[cfg(windows_platform)]
impl DeviceIdExtWindows for DeviceId {
#[inline]
fn persistent_identifier(&self) -> Option<String> {
self.0.persistent_identifier()
}
}
/// Additional methods on `FingerId` that are specific to Windows.
pub trait FingerIdExtWindows {
/// Indicates if the finger represents the first contact in a multi-touch interaction.
#[allow(clippy::wrong_self_convention)]
fn is_primary(self) -> bool;
}
impl FingerIdExtWindows for FingerId {
#[inline]
fn is_primary(self) -> bool {
self.0.is_primary()
let raw_id = self.into_raw();
if raw_id != 0 {
crate::platform_impl::raw_input::get_raw_input_device_name(raw_id as HANDLE)
} else {
None
}
}
}

View File

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::dpi::Size;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
use crate::window::{Window as CoreWindow, WindowAttributes};
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
@@ -80,9 +80,7 @@ pub type XWindow = u32;
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
@@ -91,7 +89,7 @@ pub trait ActiveEventLoopExtX11 {
fn is_x11(&self) -> bool;
}
impl ActiveEventLoopExtX11 for &dyn ActiveEventLoop {
impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ {
#[inline]
fn is_x11(&self) -> bool {
self.as_any().downcast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
@@ -138,9 +136,11 @@ impl EventLoopBuilderExtX11 for EventLoopBuilder {
}
/// Additional methods on [`Window`] that are specific to X11.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtX11 {}
impl WindowExtX11 for Window {}
impl WindowExtX11 for dyn CoreWindow {}
/// Additional methods on [`WindowAttributes`] that are specific to X11.
pub trait WindowAttributesExtX11 {
@@ -169,13 +169,13 @@ pub trait WindowAttributesExtX11 {
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use winit::window::{Window, WindowAttributes};
/// # use winit::platform::x11::WindowAttributesExtX11;
/// // Specify the size in logical dimensions like this:
/// Window::default_attributes().with_base_size(LogicalSize::new(400.0, 200.0));
/// WindowAttributes::default().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// Window::default_attributes().with_base_size(PhysicalSize::new(400, 200));
/// WindowAttributes::default().with_base_size(PhysicalSize::new(400, 200));
/// ```
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
@@ -184,12 +184,12 @@ pub trait WindowAttributesExtX11 {
/// # Example
///
/// ```no_run
/// use winit::window::Window;
/// use winit::window::{Window, WindowAttributes};
/// use winit::event_loop::ActiveEventLoop;
/// use winit::platform::x11::{XWindow, WindowAttributesExtX11};
/// # fn create_window(event_loop: &dyn ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window_attributes = Window::default_attributes().with_embed_parent_window(parent_window_id);
/// let window_attributes = WindowAttributes::default().with_embed_parent_window(parent_window_id);
/// let window = event_loop.create_window(window_attributes)?;
/// # Ok(()) }
/// ```

View File

@@ -1,4 +1,3 @@
use std::any::Any;
use std::cell::Cell;
use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
@@ -14,19 +13,20 @@ use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{self, EventLoopError, ExternalError, NotSupportedError};
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
Window as RootWindow, WindowAttributes, WindowButtons, WindowLevel,
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
};
mod keycodes;
@@ -108,6 +108,7 @@ pub struct EventLoop {
running: bool,
pending_redraw: bool,
cause: StartCause,
primary_pointer: Option<FingerId>,
ignore_volume_keys: bool,
combining_accent: Option<char>,
}
@@ -124,26 +125,31 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
// Android currently only supports one window
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android",
);
let event_loop_proxy = Arc::new(EventLoopProxy::new(android_app.create_waker()));
let redraw_flag = SharedFlag::new();
Ok(Self {
android_app: android_app.clone(),
primary_pointer: None,
window_target: ActiveEventLoop {
app: android_app.clone(),
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(false),
redraw_requester: RedrawRequester::new(&redraw_flag, android_app.create_waker()),
proxy_wake_up,
event_loop_proxy,
},
redraw_flag,
loop_running: false,
@@ -189,41 +195,38 @@ impl EventLoop {
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(&self.window_target, window_id, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(&self.window_target, window_id, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
MainEvent::ConfigChanged { .. } => {
let old_scale_factor = scale_factor(&self.android_app);
let scale_factor = scale_factor(&self.android_app);
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
let new_inner_size = Arc::new(Mutex::new(screen_size(&self.android_app)));
let window_id = window::WindowId(WindowId);
let new_surface_size = Arc::new(Mutex::new(screen_size(&self.android_app)));
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(
&new_surface_size,
)),
scale_factor,
};
app.window_event(&self.window_target, window_id, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}
},
MainEvent::LowMemory => {
app.memory_warning(&self.window_target);
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStart notification to application");
app.resumed(self.window_target());
},
MainEvent::Resume { .. } => {
debug!("App Resumed - is running");
// TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
self.running = true;
},
MainEvent::SaveState { .. } => {
@@ -233,11 +236,11 @@ impl EventLoop {
},
MainEvent::Pause => {
debug!("App Paused - stopped running");
// TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
self.running = false;
},
MainEvent::Stop => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStop notification to application");
app.suspended(self.window_target());
},
MainEvent::Destroy => {
// XXX: maybe exit mainloop to drop things before being
@@ -275,7 +278,7 @@ impl EventLoop {
},
}
if self.window_target.proxy_wake_up.swap(false, Ordering::Relaxed) {
if self.window_target.event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(&self.window_target);
}
@@ -288,17 +291,15 @@ impl EventLoop {
} else {
PhysicalSize::new(0, 0)
};
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(&self.window_target, window_id, event);
let event = event::WindowEvent::SurfaceResized(size);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(&self.window_target, window_id, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}
}
@@ -317,50 +318,131 @@ impl EventLoop {
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
let window_id = window::WindowId(WindowId);
let device_id = event::DeviceId(DeviceId(motion_event.device_id()));
let device_id = Some(DeviceId::from_raw(motion_event.device_id() as i64));
let action = motion_event.action();
let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started)
},
MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended),
MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => Some(event::TouchPhase::Cancelled),
_ => {
None // TODO mouse events
let pointers: Option<
Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>>,
> = match action {
MotionAction::Down
| MotionAction::PointerDown
| MotionAction::Up
| MotionAction::PointerUp => Some(Box::new(std::iter::once(
motion_event.pointer_at_index(motion_event.pointer_index()),
))),
MotionAction::Move | MotionAction::Cancel => {
Some(Box::new(motion_event.pointers()))
},
// TODO mouse events
_ => None,
};
if let Some(phase) = phase {
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
match phase {
event::TouchPhase::Started | event::TouchPhase::Ended => {
Box::new(std::iter::once(
motion_event.pointer_at_index(motion_event.pointer_index()),
))
},
event::TouchPhase::Moved | event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers())
},
};
for pointer in pointers {
let location =
PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
trace!(
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
for pointer in pointers.into_iter().flatten() {
let tool_type = pointer.tool_type();
let position = PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
trace!(
"Input event {device_id:?}, {action:?}, loc={position:?}, \
pointer={pointer:?}, tool_type={tool_type:?}"
);
let finger_id = FingerId::from_raw(pointer.pointer_id() as usize);
let force = Some(Force::Normalized(pointer.pressure() as f64));
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
finger_id: event::FingerId(FingerId(pointer.pointer_id())),
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
match action {
MotionAction::Down | MotionAction::PointerDown => {
let primary = action == MotionAction::Down;
if primary {
self.primary_pointer = Some(finger_id);
}
let event = event::WindowEvent::PointerEntered {
device_id,
primary,
position,
kind: match tool_type {
android_activity::input::ToolType::Finger => {
event::PointerKind::Touch(finger_id)
},
// TODO mouse events
android_activity::input::ToolType::Mouse => continue,
_ => event::PointerKind::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
let event = event::WindowEvent::PointerButton {
device_id,
primary,
state: event::ElementState::Pressed,
position,
button: match tool_type {
android_activity::input::ToolType::Finger => {
event::ButtonSource::Touch { finger_id, force }
},
// TODO mouse events
android_activity::input::ToolType::Mouse => continue,
_ => event::ButtonSource::Unknown(0),
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
MotionAction::Move => {
let primary = self.primary_pointer == Some(finger_id);
let event = event::WindowEvent::PointerMoved {
device_id,
primary,
position,
source: match tool_type {
android_activity::input::ToolType::Finger => {
event::PointerSource::Touch { finger_id, force }
},
// TODO mouse events
android_activity::input::ToolType::Mouse => continue,
_ => event::PointerSource::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
MotionAction::Up | MotionAction::PointerUp | MotionAction::Cancel => {
let primary = action == MotionAction::Up
|| (action == MotionAction::Cancel
&& self.primary_pointer == Some(finger_id));
app.window_event(&self.window_target, window_id, event);
if primary {
self.primary_pointer = None;
}
if let MotionAction::Up | MotionAction::PointerUp = action {
let event = event::WindowEvent::PointerButton {
device_id,
primary,
state: event::ElementState::Released,
position,
button: match tool_type {
android_activity::input::ToolType::Finger => {
event::ButtonSource::Touch { finger_id, force }
},
// TODO mouse events
android_activity::input::ToolType::Mouse => continue,
_ => event::ButtonSource::Unknown(0),
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
}
let event = event::WindowEvent::PointerLeft {
device_id,
primary,
position: Some(position),
kind: match tool_type {
android_activity::input::ToolType::Finger => {
event::PointerKind::Touch(finger_id)
},
// TODO mouse events
android_activity::input::ToolType::Mouse => continue,
_ => event::PointerKind::Unknown,
},
};
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
_ => unreachable!(),
}
}
},
@@ -388,9 +470,8 @@ impl EventLoop {
&mut self.combining_accent,
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
@@ -403,7 +484,7 @@ impl EventLoop {
is_synthetic: false,
};
app.window_event(&self.window_target, window_id, event);
app.window_event(&self.window_target, GLOBAL_WINDOW, event);
},
}
},
@@ -483,7 +564,8 @@ impl EventLoop {
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout = if self.running
&& (self.pending_redraw || self.window_target.proxy_wake_up.load(Ordering::Relaxed))
&& (self.pending_redraw
|| self.window_target.event_loop_proxy.wake_up.load(Ordering::Relaxed))
{
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
@@ -516,7 +598,7 @@ impl EventLoop {
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw
&& !self.window_target.proxy_wake_up.load(Ordering::Relaxed))
&& !self.window_target.event_loop_proxy.wake_up.load(Ordering::Relaxed))
{
return;
}
@@ -555,15 +637,20 @@ impl EventLoop {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
wake_up: AtomicBool,
waker: AndroidAppWaker,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, Ordering::Relaxed);
fn new(waker: AndroidAppWaker) -> Self {
Self { wake_up: AtomicBool::new(false), waker }
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, Ordering::Relaxed);
self.waker.wake();
}
}
@@ -573,7 +660,7 @@ pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
proxy_wake_up: Arc<AtomicBool>,
event_loop_proxy: Arc<EventLoopProxy>,
}
impl ActiveEventLoop {
@@ -583,27 +670,22 @@ impl ActiveEventLoop {
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> RootEventLoopProxy {
let event_loop_proxy = EventLoopProxy {
proxy_wake_up: self.proxy_wake_up.clone(),
waker: self.app.create_waker(),
};
RootEventLoopProxy { event_loop_proxy }
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(self.event_loop_proxy.clone())
}
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<RootWindow, error::OsError> {
let window = Window::new(self, window_attributes)?;
Ok(RootWindow { window })
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_source: CustomCursorSource,
) -> Result<CustomCursor, ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
) -> Result<CustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
@@ -636,21 +718,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
self.exit.get()
}
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::AndroidDisplayHandle::new();
@@ -661,52 +737,10 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AndroidDisplayHandle::new().into())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct WindowId;
impl WindowId {
pub const fn dummy() -> Self {
WindowId
}
}
impl From<WindowId> for u64 {
fn from(_: WindowId) -> Self {
0
}
}
impl From<u64> for WindowId {
fn from(_: u64) -> Self {
Self
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId(i32);
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId(0)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FingerId(i32);
impl FingerId {
pub const fn dummy() -> Self {
FingerId(0)
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::AndroidDisplayHandle::new();
Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw.into()) })
}
}
@@ -722,178 +756,23 @@ impl Window {
pub(crate) fn new(
el: &ActiveEventLoop,
_window_attrs: window::WindowAttributes,
) -> Result<Self, error::OsError> {
) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
f(self)
pub fn config(&self) -> ConfigurationRef {
self.app.config()
}
pub(crate) fn maybe_wait_on_main<R: Send>(&self, f: impl FnOnce(&Self) -> R + Send) -> R {
f(self)
pub fn content_rect(&self) -> Rect {
self.app.content_rect()
}
pub fn id(&self) -> WindowId {
WindowId
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
None
}
pub fn available_monitors(&self) -> Option<MonitorHandle> {
None
}
pub fn current_monitor(&self) -> Option<MonitorHandle> {
None
}
pub fn scale_factor(&self) -> f64 {
scale_factor(&self.app)
}
pub fn request_redraw(&self) {
self.redraw_requester.request_redraw()
}
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
Err(error::NotSupportedError::new())
}
pub fn set_outer_position(&self, _position: Position) {
// no effect
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
self.outer_size()
}
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
screen_size(&self.app)
}
pub fn set_min_inner_size(&self, _: Option<Size>) {}
pub fn set_max_inner_size(&self, _: Option<Size>) {}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
pub fn set_title(&self, _title: &str) {}
pub fn set_transparent(&self, _transparent: bool) {}
pub fn set_blur(&self, _blur: bool) {}
pub fn set_visible(&self, _visibility: bool) {}
pub fn is_visible(&self) -> Option<bool> {
None
}
pub fn set_resizable(&self, _resizeable: bool) {}
pub fn is_resizable(&self) -> bool {
false
}
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {}
pub fn enabled_buttons(&self) -> WindowButtons {
WindowButtons::all()
}
pub fn set_minimized(&self, _minimized: bool) {}
pub fn is_minimized(&self) -> Option<bool> {
None
}
pub fn set_maximized(&self, _maximized: bool) {}
pub fn is_maximized(&self) -> bool {
false
}
pub fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {
warn!("Cannot set fullscreen on Android");
}
pub fn fullscreen(&self) -> Option<Fullscreen> {
None
}
pub fn set_decorations(&self, _decorations: bool) {}
pub fn is_decorated(&self) -> bool {
true
}
pub fn set_window_level(&self, _level: WindowLevel) {}
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
pub fn focus_window(&self) {}
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn set_cursor_visible(&self, _: bool) {}
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn drag_resize_window(
&self,
_direction: ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[cfg(feature = "rwh_06")]
// Allow the usage of HasRawWindowHandle inside this function
#[allow(deprecated)]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
use rwh_06::HasRawWindowHandle;
if let Some(native_window) = self.app.native_window().as_ref() {
@@ -908,38 +787,212 @@ impl Window {
}
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
fn raw_display_handle_rwh_06(&self) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
}
}
pub fn config(&self) -> ConfigurationRef {
self.app.config()
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> WindowId {
GLOBAL_WINDOW
}
pub fn content_rect(&self) -> Rect {
self.app.content_rect()
}
pub fn set_theme(&self, _theme: Option<Theme>) {}
pub fn theme(&self) -> Option<Theme> {
fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None
}
pub fn set_content_protected(&self, _protected: bool) {}
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty())
}
pub fn has_focus(&self) -> bool {
fn current_monitor(&self) -> Option<RootMonitorHandle> {
None
}
fn scale_factor(&self) -> f64 {
scale_factor(&self.app)
}
fn request_redraw(&self) {
self.redraw_requester.request_redraw()
}
fn pre_present_notify(&self) {}
fn surface_position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
}
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
Err(NotSupportedError::new("outer_position is not supported").into())
}
fn set_outer_position(&self, _position: Position) {
// no effect
}
fn surface_size(&self) -> PhysicalSize<u32> {
self.outer_size()
}
fn request_surface_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.surface_size())
}
fn outer_size(&self) -> PhysicalSize<u32> {
screen_size(&self.app)
}
fn safe_area(&self) -> PhysicalInsets<u32> {
PhysicalInsets::new(0, 0, 0, 0)
}
fn set_min_surface_size(&self, _: Option<Size>) {}
fn set_max_surface_size(&self, _: Option<Size>) {}
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
fn set_surface_resize_increments(&self, _increments: Option<Size>) {}
fn set_title(&self, _title: &str) {}
fn set_transparent(&self, _transparent: bool) {}
fn set_blur(&self, _blur: bool) {}
fn set_visible(&self, _visibility: bool) {}
fn is_visible(&self) -> Option<bool> {
None
}
fn set_resizable(&self, _resizeable: bool) {}
fn is_resizable(&self) -> bool {
false
}
fn set_enabled_buttons(&self, _buttons: WindowButtons) {}
fn enabled_buttons(&self) -> WindowButtons {
WindowButtons::all()
}
fn set_minimized(&self, _minimized: bool) {}
fn is_minimized(&self) -> Option<bool> {
None
}
fn set_maximized(&self, _maximized: bool) {}
fn is_maximized(&self) -> bool {
false
}
fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {
warn!("Cannot set fullscreen on Android");
}
fn fullscreen(&self) -> Option<Fullscreen> {
None
}
fn set_decorations(&self, _decorations: bool) {}
fn is_decorated(&self) -> bool {
true
}
fn set_window_level(&self, _level: WindowLevel) {}
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
}
}
fn set_ime_purpose(&self, _purpose: ImePurpose) {}
fn focus_window(&self) {}
fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
fn set_cursor(&self, _: Cursor) {}
fn set_cursor_position(&self, _: Position) -> Result<(), RequestError> {
Err(NotSupportedError::new("set_cursor_position is not supported").into())
}
fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), RequestError> {
Err(NotSupportedError::new("set_cursor_grab is not supported").into())
}
fn set_cursor_visible(&self, _: bool) {}
fn drag_window(&self) -> Result<(), RequestError> {
Err(NotSupportedError::new("drag_window is not supported").into())
}
fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), RequestError> {
Err(NotSupportedError::new("drag_resize_window").into())
}
#[inline]
fn show_window_menu(&self, _position: Position) {}
fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), RequestError> {
Err(NotSupportedError::new("set_cursor_hittest is not supported").into())
}
fn set_theme(&self, _theme: Option<Theme>) {}
fn theme(&self) -> Option<Theme> {
None
}
fn set_content_protected(&self, _protected: bool) {}
fn has_focus(&self) -> bool {
HAS_FOCUS.load(Ordering::Relaxed)
}
pub fn title(&self) -> String {
fn title(&self) -> String {
String::new()
}
pub fn reset_dead_keys(&self) {}
fn reset_dead_keys(&self) {}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
#[derive(Default, Clone, Debug)]

View File

@@ -1,11 +1,12 @@
#![allow(clippy::unnecessary_cast)]
use std::rc::Rc;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::ApplicationDelegate;
use super::DEVICE_ID;
use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
@@ -38,15 +39,15 @@ declare_class!(
key_window.sendEvent(event);
}
} else {
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&delegate, event);
let app_state = AppState::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&app_state, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
}
}
);
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = unsafe { event.r#type() };
#[allow(non_upper_case_globals)]
match event_type {
@@ -58,8 +59,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 || delta_y != 0.0 {
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y),
});
});
@@ -67,8 +68,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
@@ -76,8 +77,8 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Released,
});

View File

@@ -1,31 +1,30 @@
use std::cell::{Cell, RefCell};
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Weak;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::rc::{Rc, Weak};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc;
use std::time::Instant;
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::{MainThreadMarker, NSNotification};
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::menu;
use super::observer::{EventLoopWaker, RunLoop};
use super::{menu, WindowId};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId as RootWindowId;
use crate::window::WindowId;
#[derive(Debug)]
pub(super) struct AppState {
activation_policy: NSApplicationActivationPolicy,
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
proxy_wake_up: Arc<AtomicBool>,
event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -46,45 +45,34 @@ pub(super) struct AppState {
// as such should be careful to not add fields that, in turn, strongly reference those.
}
declare_class!(
#[derive(Debug)]
pub(super) struct ApplicationDelegate;
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplicationDelegate";
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = AppState;
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
// main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
StaticMainThreadBound(OnceCell::new());
unsafe impl NSApplicationDelegate for ApplicationDelegate {
#[method(applicationDidFinishLaunching:)]
fn app_did_finish_launching(&self, notification: &NSNotification) {
self.did_finish_launching(notification)
}
#[method(applicationWillTerminate:)]
fn app_will_terminate(&self, notification: &NSNotification) {
self.will_terminate(notification)
}
}
);
impl ApplicationDelegate {
pub(super) fn new(
impl AppState {
pub(super) fn setup_global(
mtm: MainThreadMarker,
activation_policy: NSApplicationActivationPolicy,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(AppState {
) -> Rc<Self> {
let this = Rc::new(AppState {
mtm,
activation_policy,
proxy_wake_up: Arc::new(AtomicBool::new(false)),
event_loop_proxy: Arc::new(EventLoopProxy::new()),
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
@@ -102,33 +90,56 @@ impl ApplicationDelegate {
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
});
unsafe { msg_send_id![super(this), init] }
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once");
this
}
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
fn did_finish_launching(&self, _notification: &NSNotification) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
GLOBAL
.get(mtm)
.get()
.expect("tried to get application state before it was registered")
.clone()
}
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy);
if let Some(activation_policy) = self.activation_policy {
app.setActivationPolicy(activation_policy);
} else {
// If no activation policy is explicitly provided, and the application
// is bundled, do not set the activation policy at all, to allow the
// package manifest to define the behavior via LSUIElement.
//
// See:
// - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958
let is_bundled =
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
}
}
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
app.activateIgnoringOtherApps(self.activate_ignoring_other_apps);
if self.ivars().default_menu {
if self.default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
@@ -138,77 +149,65 @@ impl ApplicationDelegate {
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
if self.stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
fn will_terminate(&self, _notification: &NSNotification) {
trace_scope!("applicationWillTerminate:");
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
unsafe { app.delegate() }.expect("a delegate was not configured on the application");
if delegate.is_kind_of::<Self>() {
// SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate`
unsafe { Retained::cast(delegate) }
} else {
panic!("tried to get a delegate that was not the one Winit has registered")
}
}
/// Place the event handler in the application delegate for the duration
/// Place the event handler in the application state for the duration
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
self.ivars().event_handler.set(handler, closure)
self.event_handler.set(handler, closure)
}
pub fn proxy_wake_up(&self) -> Arc<AtomicBool> {
self.ivars().proxy_wake_up.clone()
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
/// If `pump_events` is called to progress the event loop then we
/// bootstrap the event loop via `-[NSApplication run]` but will use
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
pub fn set_stop_on_launch(&self) {
self.ivars().stop_on_launch.set(true);
self.stop_on_launch.set(true);
}
pub fn set_stop_before_wait(&self, value: bool) {
self.ivars().stop_before_wait.set(value)
self.stop_before_wait.set(value)
}
pub fn set_stop_after_wait(&self, value: bool) {
self.ivars().stop_after_wait.set(value)
self.stop_after_wait.set(value)
}
pub fn set_stop_on_redraw(&self, value: bool) {
self.ivars().stop_on_redraw.set(value)
self.stop_on_redraw.set(value)
}
pub fn set_wait_timeout(&self, value: Option<Instant>) {
self.ivars().wait_timeout.set(value)
self.wait_timeout.set(value)
}
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
///
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(&self) {
pub fn internal_exit(self: &Rc<Self>) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
@@ -221,67 +220,66 @@ impl ApplicationDelegate {
}
pub fn is_launched(&self) -> bool {
self.ivars().is_launched.get()
self.is_launched.get()
}
pub fn set_is_running(&self, value: bool) {
self.ivars().is_running.set(value)
self.is_running.set(value)
}
pub fn is_running(&self) -> bool {
self.ivars().is_running.get()
self.is_running.get()
}
pub fn exit(&self) {
self.ivars().exit.set(true)
self.exit.set(true)
}
pub fn clear_exit(&self) {
self.ivars().exit.set(false)
self.exit.set(false)
}
pub fn exiting(&self) -> bool {
self.ivars().exit.get()
self.exit.get()
}
pub fn set_control_flow(&self, value: ControlFlow) {
self.ivars().control_flow.set(value)
self.control_flow.set(value)
}
pub fn control_flow(&self) -> ControlFlow {
self.ivars().control_flow.get()
self.control_flow.get()
}
pub fn handle_redraw(&self, window_id: WindowId) {
let mtm = MainThreadMarker::from(self);
pub fn handle_redraw(self: &Rc<Self>, window_id: WindowId) {
// Redraw request might come out of order from the OS.
// -> Don't go back into the event handler when our callstack originates from there
if !self.ivars().event_handler.in_use() {
if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
// events as a way to ensure that `pump_events` can't block an external loop
// indefinitely
if self.ivars().stop_on_redraw.get() {
let app = NSApplication::sharedApplication(mtm);
if self.stop_on_redraw.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
}
pub fn queue_redraw(&self, window_id: WindowId) {
let mut pending_redraw = self.ivars().pending_redraw.borrow_mut();
let mut pending_redraw = self.pending_redraw.borrow_mut();
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.ivars().run_loop.wakeup();
self.run_loop.wakeup();
}
#[track_caller]
pub fn maybe_queue_with_handler(
&self,
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
@@ -290,26 +288,28 @@ impl ApplicationDelegate {
// However, it is not documented which actions do this, and which ones are done immediately,
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.ivars().event_handler.in_use() {
if !self.event_handler.in_use() {
self.with_handler(callback);
} else {
tracing::debug!("had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || {
let this = Rc::clone(self);
self.run_loop.queue_closure(move || {
this.with_handler(callback);
});
}
}
#[track_caller]
fn with_handler(&self, callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop)) {
let event_loop =
ActiveEventLoop { delegate: self.retain(), mtm: MainThreadMarker::from(self) };
self.ivars().event_handler.handle(callback, &event_loop);
fn with_handler(
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
) {
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
self.event_handler.handle(|app| callback(app, &event_loop));
}
/// dispatch `NewEvents(Init)` + `Resumed`
pub fn dispatch_init_events(&self) {
pub fn dispatch_init_events(self: &Rc<Self>) {
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
// NB: For consistency all platforms must call `can_create_surfaces` even though macOS
// applications don't themselves have a formal surface destroy/create lifecycle.
@@ -317,23 +317,22 @@ impl ApplicationDelegate {
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(&self, panic_info: Weak<PanicInfo>) {
let mtm = MainThreadMarker::from(self);
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return;
}
if self.ivars().stop_after_wait.get() {
let app = NSApplication::sharedApplication(mtm);
if self.stop_after_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
let start = self.ivars().start_time.get().unwrap();
let start = self.start_time.get().unwrap();
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
@@ -350,8 +349,7 @@ impl ApplicationDelegate {
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(&self, panic_info: Weak<PanicInfo>) {
let mtm = MainThreadMarker::from(self);
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
@@ -359,18 +357,18 @@ impl ApplicationDelegate {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return;
}
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
}
self.with_handler(|app, event_loop| {
@@ -378,22 +376,22 @@ impl ApplicationDelegate {
});
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
if self.ivars().stop_before_wait.get() {
let app = NSApplication::sharedApplication(mtm);
if self.stop_before_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
self.ivars().start_time.set(Some(Instant::now()));
let wait_timeout = self.ivars().wait_timeout.get(); // configured by pump_events
self.start_time.set(Some(Instant::now()));
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
let app_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
}
}
@@ -403,26 +401,3 @@ impl ApplicationDelegate {
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
/// A hack to make activation of multiple windows work when creating them before
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
///
/// Alternative to this would be the user calling `window.set_visible(true)` in
/// `StartCause::Init`.
///
/// If this becomes too bothersome to maintain, it can probably be removed
/// without too much damage.
fn window_activation_hack(app: &NSApplication) {
// TODO: Proper ordering of the windows
app.windows().into_iter().for_each(|window| {
// Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
// This way we preserve the user's desired initial visibility status
// TODO: Also filter on the type/"level" of the window, and maybe other things?
if window.isVisible() {
tracing::trace!("Activating visible window");
window.makeKeyAndOrderFront(None);
} else {
tracing::trace!("Skipping activating invisible window");
}
})
}

View File

@@ -11,9 +11,8 @@ use objc2_foundation::{
NSString,
};
use super::OsError;
use crate::cursor::{CursorImage, OnlyCursorImageSource};
use crate::error::ExternalError;
use crate::error::RequestError;
use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -25,12 +24,12 @@ unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, ExternalError> {
pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, RequestError> {
cursor_from_image(&cursor.0).map(Self)
}
}
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, ExternalError> {
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> {
let width = cursor.width;
let height = cursor.height;
@@ -48,7 +47,7 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCurso
width as isize * 4,
32,
)
}.ok_or_else(|| ExternalError::Os(os_error!(OsError::CreationError("parent view should be installed in a window"))))?;
}.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);

View File

@@ -92,17 +92,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`.
///
/// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
let mut physical_key = scancode_to_physicalkey(scancode as u32);
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
@@ -111,20 +106,15 @@ pub(crate) fn create_key_event(
// * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best.
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
None
} else {
let characters =
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
};
let key_from_code = code_to_key(physical_key, scancode);
@@ -377,6 +367,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::KeyX => Some(0x07),
KeyCode::KeyC => Some(0x08),
KeyCode::KeyV => Some(0x09),
KeyCode::IntlBackslash => Some(0x0a),
KeyCode::KeyB => Some(0x0b),
KeyCode::KeyQ => Some(0x0c),
KeyCode::KeyW => Some(0x0d),
@@ -422,18 +413,21 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::SuperRight => Some(0x36),
KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a),
KeyCode::ControlLeft => Some(0x3b),
KeyCode::ShiftRight => Some(0x3c),
KeyCode::AltRight => Some(0x3d),
KeyCode::ControlRight => Some(0x3e),
KeyCode::Fn => Some(0x3f),
KeyCode::F17 => Some(0x40),
KeyCode::NumpadDecimal => Some(0x41),
KeyCode::NumpadMultiply => Some(0x43),
KeyCode::NumpadAdd => Some(0x45),
KeyCode::NumLock => Some(0x47),
KeyCode::AudioVolumeUp => Some(0x49),
KeyCode::AudioVolumeDown => Some(0x4a),
KeyCode::AudioVolumeUp => Some(0x48),
KeyCode::AudioVolumeDown => Some(0x49),
KeyCode::AudioVolumeMute => Some(0x4a),
KeyCode::NumpadDivide => Some(0x4b),
KeyCode::NumpadEnter => Some(0x4c),
KeyCode::NumpadSubtract => Some(0x4e),
@@ -452,17 +446,22 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::Numpad8 => Some(0x5b),
KeyCode::Numpad9 => Some(0x5c),
KeyCode::IntlYen => Some(0x5d),
KeyCode::IntlRo => Some(0x5e),
KeyCode::NumpadComma => Some(0x5f),
KeyCode::F5 => Some(0x60),
KeyCode::F6 => Some(0x61),
KeyCode::F7 => Some(0x62),
KeyCode::F3 => Some(0x63),
KeyCode::F8 => Some(0x64),
KeyCode::F9 => Some(0x65),
KeyCode::Lang2 => Some(0x66),
KeyCode::F11 => Some(0x67),
KeyCode::Lang1 => Some(0x68),
KeyCode::F13 => Some(0x69),
KeyCode::F16 => Some(0x6a),
KeyCode::F14 => Some(0x6b),
KeyCode::F10 => Some(0x6d),
KeyCode::ContextMenu => Some(0x6e),
KeyCode::F12 => Some(0x6f),
KeyCode::F15 => Some(0x71),
KeyCode::Insert => Some(0x72),
@@ -478,11 +477,26 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
KeyCode::ArrowRight => Some(0x7c),
KeyCode::ArrowDown => Some(0x7d),
KeyCode::ArrowUp => Some(0x7e),
KeyCode::Power => Some(0x7f),
_ => None,
}
}
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// See also:
// Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
//
// Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values:
//
// > the system handles some function keys at a lower level and your app never sees them.
// > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and
// > Function key found on many Macs.
//
// So the handling of some of these is mostly for show.
PhysicalKey::Code(match scancode {
0x00 => KeyCode::KeyA,
0x01 => KeyCode::KeyS,
@@ -494,7 +508,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x07 => KeyCode::KeyX,
0x08 => KeyCode::KeyC,
0x09 => KeyCode::KeyV,
// 0x0a => World 1,
// This key is typically located near LeftShift key, roughly the same location as backquote
// (`) on Windows' US layout.
//
// The keycap varies on international keyboards.
0x0a => KeyCode::IntlBackslash,
0x0b => KeyCode::KeyB,
0x0c => KeyCode::KeyQ,
0x0d => KeyCode::KeyW,
@@ -536,7 +554,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x31 => KeyCode::Space,
0x32 => KeyCode::Backquote,
0x33 => KeyCode::Backspace,
// 0x34 => unknown,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape,
0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft,
@@ -555,15 +573,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 0x44 => unknown,
0x45 => KeyCode::NumpadAdd,
// 0x46 => unknown,
0x47 => KeyCode::NumLock,
// 0x48 => KeyCode::NumpadClear,
// TODO: (Artur) for me, kVK_VolumeUp is 0x48
// macOS 10.11
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
// Versions/A/Headers/Events.h
0x49 => KeyCode::AudioVolumeUp,
0x4a => KeyCode::AudioVolumeDown,
0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear
0x48 => KeyCode::AudioVolumeUp,
0x49 => KeyCode::AudioVolumeDown,
0x4a => KeyCode::AudioVolumeMute,
0x4b => KeyCode::NumpadDivide,
0x4c => KeyCode::NumpadEnter,
// 0x4d => unknown,
@@ -583,23 +596,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x5b => KeyCode::Numpad8,
0x5c => KeyCode::Numpad9,
0x5d => KeyCode::IntlYen,
// 0x5e => JIS Ro,
// 0x5f => unknown,
0x5e => KeyCode::IntlRo,
0x5f => KeyCode::NumpadComma,
0x60 => KeyCode::F5,
0x61 => KeyCode::F6,
0x62 => KeyCode::F7,
0x63 => KeyCode::F3,
0x64 => KeyCode::F8,
0x65 => KeyCode::F9,
// 0x66 => JIS Eisuu (macOS),
0x66 => KeyCode::Lang2,
0x67 => KeyCode::F11,
// 0x68 => JIS Kanna (macOS),
0x68 => KeyCode::Lang1,
0x69 => KeyCode::F13,
0x6a => KeyCode::F16,
0x6b => KeyCode::F14,
// 0x6c => unknown,
0x6d => KeyCode::F10,
// 0x6e => unknown,
0x6e => KeyCode::ContextMenu,
0x6f => KeyCode::F12,
// 0x70 => unknown,
0x71 => KeyCode::F15,
@@ -616,11 +629,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x7c => KeyCode::ArrowRight,
0x7d => KeyCode::ArrowDown,
0x7e => KeyCode::ArrowUp,
// 0x7f => unknown,
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout.
0xa => KeyCode::Backquote,
0x7f => KeyCode::Power, // On 10.7 and 10.8 only
_ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)),
})
}

View File

@@ -14,30 +14,33 @@ use core_foundation::runloop::{
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow,
};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app::WinitApplication;
use super::app_state::ApplicationDelegate;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError};
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as RootWindow,
};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
@@ -71,15 +74,11 @@ impl PanicInfo {
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) delegate: Retained<ApplicationDelegate>,
pub(super) app_state: Rc<AppState>,
pub(super) mtm: MainThreadMarker,
}
impl ActiveEventLoop {
pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
&self.delegate
}
pub(crate) fn hide_application(&self) {
NSApplication::sharedApplication(self.mtm).hide(None)
}
@@ -98,23 +97,21 @@ impl ActiveEventLoop {
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> RootEventLoopProxy {
let event_loop_proxy = EventLoopProxy::new(self.delegate.proxy_wake_up());
RootEventLoopProxy { event_loop_proxy }
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<crate::window::Window, crate::error::OsError> {
let window = Window::new(self, window_attributes)?;
Ok(RootWindow { window })
) -> Result<Box<dyn crate::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<RootCustomCursor, ExternalError> {
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
}
@@ -140,36 +137,30 @@ impl RootActiveEventLoop for ActiveEventLoop {
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.delegate.set_control_flow(control_flow)
self.app_state.set_control_flow(control_flow)
}
fn control_flow(&self) -> ControlFlow {
self.delegate.control_flow()
self.app_state.control_flow()
}
fn exit(&self) {
self.delegate.exit()
self.app_state.exit()
}
fn exiting(&self) -> bool {
self.delegate.exiting()
self.app_state.exiting()
}
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
@@ -183,30 +174,29 @@ pub struct EventLoop {
/// We intentionally don't store `WinitApplication` since we want to have
/// the possibility of swapping that out at some point.
app: Retained<NSApplication>,
/// The application delegate that we've registered.
///
/// The delegate is only weakly referenced by NSApplication, so we must
/// keep it around here as well.
delegate: Retained<ApplicationDelegate>,
app_state: Rc<AppState>,
window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
// the system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: ActivationPolicy,
pub(crate) activation_policy: Option<ActivationPolicy>,
pub(crate) default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self {
activation_policy: Default::default(), // Regular
default_menu: true,
activate_ignoring_other_apps: true,
}
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
}
}
@@ -228,30 +218,55 @@ impl EventLoop {
}
let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
};
let delegate = ApplicationDelegate::new(
let app_state = AppState::setup_global(
mtm,
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
autoreleasepool(|_| {
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
});
let center = unsafe { NSNotificationCenter::defaultCenter() };
let weak_app_state = Rc::downgrade(&app_state);
let _did_finish_launching_observer = create_observer(
&center,
// `applicationDidFinishLaunching:`
unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| {
if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification);
}
},
);
let weak_app_state = Rc::downgrade(&app_state);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { NSApplicationWillTerminateNotification },
move |notification| {
if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification);
}
},
);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
Ok(EventLoop {
app,
delegate: delegate.clone(),
window_target: ActiveEventLoop { delegate, mtm },
app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer,
_will_terminate_observer,
})
}
@@ -271,19 +286,19 @@ impl EventLoop {
&mut self,
mut app: A,
) -> Result<(), EventLoopError> {
self.delegate.clear_exit();
self.delegate.set_event_handler(&mut app, || {
self.app_state.clear_exit();
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.delegate.set_wait_timeout(None);
self.delegate.set_stop_before_wait(false);
self.delegate.set_stop_after_wait(false);
self.delegate.set_stop_on_redraw(false);
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(false);
self.app_state.set_stop_on_redraw(false);
if self.delegate.is_launched() {
debug_assert!(!self.delegate.is_running());
self.delegate.set_is_running(true);
self.delegate.dispatch_init_events();
if self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
}
// SAFETY: We do not run the application re-entrantly
@@ -298,7 +313,7 @@ impl EventLoop {
resume_unwind(panic);
}
self.delegate.internal_exit()
self.app_state.internal_exit()
})
});
@@ -310,47 +325,47 @@ impl EventLoop {
timeout: Option<Duration>,
mut app: A,
) -> PumpStatus {
self.delegate.set_event_handler(&mut app, || {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
if !self.delegate.is_launched() {
debug_assert!(!self.delegate.is_running());
if !self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.delegate.set_stop_on_launch();
self.app_state.set_stop_on_launch();
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched
} else if !self.delegate.is_running() {
} else if !self.app_state.is_running() {
// Even though the application may have been launched, it's possible we aren't
// running if the `EventLoop` was run before and has since
// exited. This indicates that we just starting to re-run
// the same `EventLoop` again.
self.delegate.set_is_running(true);
self.delegate.dispatch_init_events();
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
} else {
// Only run for as long as the given `Duration` allows so we don't block the
// external loop.
match timeout {
Some(Duration::ZERO) => {
self.delegate.set_wait_timeout(None);
self.delegate.set_stop_before_wait(true);
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(true);
},
Some(duration) => {
self.delegate.set_stop_before_wait(false);
self.app_state.set_stop_before_wait(false);
let timeout = Instant::now() + duration;
self.delegate.set_wait_timeout(Some(timeout));
self.delegate.set_stop_after_wait(true);
self.app_state.set_wait_timeout(Some(timeout));
self.app_state.set_stop_after_wait(true);
},
None => {
self.delegate.set_wait_timeout(None);
self.delegate.set_stop_before_wait(false);
self.delegate.set_stop_after_wait(true);
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(true);
},
}
self.delegate.set_stop_on_redraw(true);
self.app_state.set_stop_on_redraw(true);
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
}
@@ -364,8 +379,8 @@ impl EventLoop {
resume_unwind(panic);
}
if self.delegate.exiting() {
self.delegate.internal_exit();
if self.app_state.exiting() {
self.app_state.internal_exit();
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
@@ -375,16 +390,12 @@ impl EventLoop {
}
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::AppKitDisplayHandle::new().into())
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
@@ -423,8 +434,9 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}
}
#[derive(Debug)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
@@ -439,14 +451,8 @@ impl Drop for EventLoopProxy {
}
}
impl Clone for EventLoopProxy {
fn clone(&self) -> Self {
EventLoopProxy::new(self.proxy_wake_up.clone())
}
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
pub(crate) fn new() -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -470,14 +476,16 @@ impl EventLoopProxy {
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
// Let the main thread know there's a new event.
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);

View File

@@ -5,7 +5,6 @@ mod app;
mod app_state;
mod cursor;
mod event;
mod event_handler;
mod event_loop;
mod ffi;
mod menu;
@@ -15,54 +14,14 @@ mod view;
mod window;
mod window_delegate;
use std::fmt;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::{Window, WindowId};
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
use crate::event::DeviceId as RootDeviceId;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
}
}
// Constant device ID; to be removed when if backend is updated to report real device IDs.
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId;
impl FingerId {
pub const fn dummy() -> Self {
FingerId
}
}
#[derive(Debug)]
pub enum OsError {
CGError(core_graphics::base::CGError),
CreationError(&'static str),
}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OsError::CGError(e) => f.pad(&format!("CGError {e}")),
OsError::CreationError(e) => f.pad(e),
}
}
}

View File

@@ -22,7 +22,7 @@ use core_foundation::runloop::{
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_state::ApplicationDelegate;
use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
@@ -59,7 +59,7 @@ extern "C" fn control_flow_begin_handler(
match activity {
kCFRunLoopAfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`");
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`");
},
_ => unreachable!(),
@@ -81,7 +81,7 @@ extern "C" fn control_flow_end_handler(
match activity {
kCFRunLoopBeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
ApplicationDelegate::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen

View File

@@ -2,36 +2,35 @@
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use objc2::rc::{Retained, WeakId};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
NSTrackingRectTag, NSView,
};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
NSSize, NSString, NSUInteger,
};
use super::app_state::ApplicationDelegate;
use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey,
scancode_to_physicalkey, KeyEventExtra,
};
use super::window::WinitWindow;
use super::DEVICE_ID;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct CursorState {
@@ -112,7 +111,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
#[derive(Debug)]
pub struct ViewState {
/// Strong reference to the global application state.
app_delegate: Retained<ApplicationDelegate>,
app_state: Rc<AppState>,
cursor_state: RefCell<CursorState>,
ime_position: Cell<NSPoint>,
@@ -135,9 +134,6 @@ pub struct ViewState {
marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view
_ns_window: WeakId<WinitWindow>,
/// The state of the `Option` as `Alt`.
option_as_alt: Cell<OptionAsAlt>,
}
@@ -178,9 +174,10 @@ declare_class!(
self.ivars().tracking_rect.set(Some(tracking_rect));
}
#[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:");
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[method(viewFrameDidChangeNotification:)]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
@@ -197,17 +194,14 @@ declare_class!(
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::Resized(size));
self.queue_event(WindowEvent::SurfaceResized(size));
}
#[method(drawRect:)]
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
if let Some(window) = self.ivars()._ns_window.load() {
self.ivars().app_delegate.handle_redraw(window.id());
}
self.ivars().app_state.handle_redraw(self.window().id());
// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
@@ -400,7 +394,7 @@ declare_class!(
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().map_or(false, |c| c.is_control());
let is_control = string.chars().next().is_some_and(|c| c.is_control());
// Commit only if we have marked text.
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
@@ -413,8 +407,9 @@ declare_class!(
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[method(doCommandBySelector:)]
fn do_command_by_selector(&self, _command: Sel) {
fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed text, since we'll end up sending
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// which is not desired given it was used to confirm IME input.
@@ -429,6 +424,18 @@ declare_class!(
// Leave preedit so that we also report the key-up for this key.
self.ivars().ime_state.set(ImeState::Ground);
}
// Send command action to user if they requested it.
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() {
handler.standard_key_binding(event_loop, window_id, command.name());
}
});
// The documentation for `-[NSTextInputClient doCommandBySelector:]` clearly states that
// we should not be forwarding this event up the responder chain, so no calling `super`
// here either.
}
}
@@ -483,9 +490,9 @@ declare_class!(
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
device_id: None,
event: key_event,
is_synthetic: false,
});
@@ -505,8 +512,8 @@ declare_class!(
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: create_key_event(&event, false, false, None),
device_id: None,
event: create_key_event(&event, false, false),
is_synthetic: false,
});
}
@@ -553,10 +560,10 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
device_id: None,
event,
is_synthetic: false,
});
@@ -638,19 +645,30 @@ declare_class!(
}
#[method(mouseEntered:)]
fn mouse_entered(&self, _event: &NSEvent) {
fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:");
self.queue_event(WindowEvent::CursorEntered {
device_id: DEVICE_ID,
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
self.queue_event(WindowEvent::PointerEntered {
device_id: None,
primary: true,
position,
kind: PointerKind::Mouse,
});
}
#[method(mouseExited:)]
fn mouse_exited(&self, _event: &NSEvent) {
fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:");
self.queue_event(WindowEvent::CursorLeft {
device_id: DEVICE_ID,
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
self.queue_event(WindowEvent::PointerLeft {
device_id: None,
primary: true,
position: Some(position),
kind: PointerKind::Mouse,
});
}
@@ -687,11 +705,11 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
device_id: None,
delta,
phase,
});
@@ -713,7 +731,7 @@ declare_class!(
};
self.queue_event(WindowEvent::PinchGesture {
device_id: DEVICE_ID,
device_id: None,
delta: unsafe { event.magnification() },
phase,
});
@@ -726,7 +744,7 @@ declare_class!(
self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
device_id: None,
});
}
@@ -746,7 +764,7 @@ declare_class!(
};
self.queue_event(WindowEvent::RotationGesture {
device_id: DEVICE_ID,
device_id: None,
delta: unsafe { event.rotation() },
phase,
});
@@ -757,7 +775,7 @@ declare_class!(
trace_scope!("pressureChangeWithEvent:");
self.queue_event(WindowEvent::TouchpadPressure {
device_id: DEVICE_ID,
device_id: None,
pressure: unsafe { event.pressure() },
stage: unsafe { event.stage() } as i64,
});
@@ -782,14 +800,13 @@ declare_class!(
impl WinitView {
pub(super) fn new(
app_delegate: &ApplicationDelegate,
window: &WinitWindow,
app_state: &Rc<AppState>,
accepts_first_mouse: bool,
option_as_alt: OptionAsAlt,
mtm: MainThreadMarker,
) -> Retained<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
app_delegate: app_delegate.retain(),
app_state: Rc::clone(app_state),
cursor_state: Default::default(),
ime_position: Default::default(),
ime_size: Default::default(),
@@ -802,39 +819,29 @@ impl WinitView {
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&this,
sel!(frameDidChange:),
Some(NSViewFrameDidChangeNotification),
Some(&this),
)
}
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<WinitWindow> {
// TODO: Simply use `window` property on `NSView`.
// That only returns a window _after_ the view has been attached though!
// (which is incompatible with `frameDidChange:`)
//
// unsafe { msg_send_id![self, window] }
self.ivars()._ns_window.load().expect("view to have a window")
let window = (**self).window().expect("view must be installed in a window");
if !window.isKindOfClass(WinitWindow::class()) {
unreachable!("view installed in non-WinitWindow");
}
// SAFETY: Just checked that the window is `WinitWindow`
unsafe { Retained::cast(window) }
}
fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
}
@@ -939,22 +946,36 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
let logical_key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some(event_modifier) = key_to_modifier(&key) else {
//
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let mut event = KeyEvent {
location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
@@ -971,7 +992,7 @@ impl WinitView {
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
device_id: None,
event,
is_synthetic: false,
});
@@ -980,7 +1001,7 @@ impl WinitView {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
device_id: None,
event,
is_synthetic: false,
});
@@ -1011,7 +1032,7 @@ impl WinitView {
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
device_id: None,
event,
is_synthetic: false,
});
@@ -1033,20 +1054,22 @@ impl WinitView {
}
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
let button = mouse_button(event);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::MouseInput {
device_id: DEVICE_ID,
self.queue_event(WindowEvent::PointerButton {
device_id: None,
primary: true,
state: button_state,
button,
position,
button: button.into(),
});
}
fn mouse_motion(&self, event: &NSEvent) {
let window_point = unsafe { event.locationInWindow() };
let view_point = self.convertPoint_fromView(window_point, None);
let view_point = self.mouse_view_point(event);
let frame = self.frame();
if view_point.x.is_sign_negative()
@@ -1061,15 +1084,22 @@ impl WinitView {
}
}
let view_point = LogicalPosition::new(view_point.x, view_point.y);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::CursorMoved {
device_id: DEVICE_ID,
self.queue_event(WindowEvent::PointerMoved {
device_id: None,
primary: true,
position: view_point.to_physical(self.scale_factor()),
source: PointerSource::Mouse,
});
}
fn mouse_view_point(&self, event: &NSEvent) -> LogicalPosition<f64> {
let window_point = unsafe { event.locationInWindow() };
let view_point = self.convertPoint_fromView(window_point, None);
LogicalPosition::new(view_point.x, view_point.y)
}
}
/// Get the mouse button from the NSEvent.

View File

@@ -1,5 +1,6 @@
#![allow(clippy::unnecessary_cast)]
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
@@ -7,8 +8,12 @@ use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::OsError as RootOsError;
use crate::window::WindowAttributes;
use crate::error::RequestError;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{
Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
@@ -16,32 +21,20 @@ pub(crate) struct Window {
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Drop for Window {
fn drop(&mut self) {
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RootOsError> {
) -> Result<Self, RequestError> {
let mtm = window_target.mtm;
let delegate = autoreleasepool(|_| {
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
})?;
let delegate =
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WindowDelegate) + 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(&WindowDelegate) -> R + Send,
@@ -49,7 +42,6 @@ impl Window {
self.delegate.get_on_main(|delegate| f(delegate))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
@@ -61,7 +53,6 @@ impl Window {
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
@@ -70,24 +61,274 @@ impl Window {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub usize);
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None);
}
impl WindowId {
pub const fn dummy() -> Self {
Self(0)
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0 as u64
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id as usize)
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
fn scale_factor(&self) -> f64 {
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
}
fn request_redraw(&self) {
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
}
fn pre_present_notify(&self) {
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
}
fn reset_dead_keys(&self) {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
fn surface_position(&self) -> dpi::PhysicalPosition<i32> {
self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
}
fn surface_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.surface_size())
}
fn request_surface_size(&self, size: Size) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
}
fn outer_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
fn safe_area(&self) -> dpi::PhysicalInsets<u32> {
self.maybe_wait_on_main(|delegate| delegate.safe_area())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
fn surface_resize_increments(&self) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
}
fn set_title(&self, title: &str) {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
fn set_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
}
fn is_visible(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_visible())
}
fn set_resizable(&self, resizable: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
}
fn is_resizable(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
}
fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
}
fn enabled_buttons(&self) -> WindowButtons {
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
}
fn set_minimized(&self, minimized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
}
fn is_minimized(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
}
fn set_maximized(&self, maximized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
}
fn is_maximized(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
}
fn set_decorations(&self, decorations: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
}
fn is_decorated(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
}
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
}
fn focus_window(&self) {
self.maybe_wait_on_main(|delegate| delegate.focus_window());
}
fn has_focus(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.has_focus())
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
}
fn set_theme(&self, theme: Option<Theme>) {
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
}
fn theme(&self) -> Option<Theme> {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
fn title(&self) -> String {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
fn set_cursor_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
}
fn drag_window(&self) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.drag_window())
}
fn drag_resize_window(
&self,
direction: crate::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
fn show_window_menu(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest));
Ok(())
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
@@ -121,6 +362,6 @@ declare_class!(
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId(self as *const Self as usize)
WindowId::from_raw(self as *const Self as usize)
}
}

View File

@@ -3,9 +3,10 @@ use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ffi::c_void;
use std::ptr;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use core_graphics::display::{CGDisplay, CGPoint};
use core_graphics::display::CGDisplay;
use monitor::VideoModeHandle;
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
@@ -14,32 +15,36 @@ use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets,
NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey,
NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming,
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
use super::app_state::ApplicationDelegate;
use super::app_state::AppState;
use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{InnerSizeWriter, WindowEvent};
use super::{ffi, Fullscreen, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
#[derive(Clone, Debug, PartialEq)]
@@ -55,6 +60,8 @@ pub struct PlatformSpecificWindowAttributes {
pub accepts_first_mouse: bool,
pub tabbing_identifier: Option<String>,
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
pub unified_titlebar: bool,
}
impl Default for PlatformSpecificWindowAttributes {
@@ -72,6 +79,8 @@ impl Default for PlatformSpecificWindowAttributes {
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
}
}
}
@@ -79,20 +88,20 @@ impl Default for PlatformSpecificWindowAttributes {
#[derive(Debug)]
pub(crate) struct State {
/// Strong reference to the global application state.
app_delegate: Retained<ApplicationDelegate>,
app_state: Rc<AppState>,
window: Retained<WinitWindow>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
// This is expressed in native screen coordinates.
previous_position: Cell<Option<NSPoint>>,
// This is expressed in desktop coordinates, and flipped to match Winit's coordinate system.
previous_position: Cell<NSPoint>,
// Used to prevent redundant events.
previous_scale_factor: Cell<f64>,
/// The current resize increments for the window content.
resize_increments: Cell<NSSize>,
surface_resize_increments: Cell<NSSize>,
/// Whether the window is showing decorations.
decorations: Cell<bool>,
resizable: Cell<bool>,
@@ -120,6 +129,7 @@ pub(crate) struct State {
standard_frame: Cell<Option<NSRect>>,
is_simple_fullscreen: Cell<bool>,
saved_style: Cell<Option<NSWindowStyleMask>>,
is_borderless_game: Cell<bool>,
}
declare_class!(
@@ -160,7 +170,7 @@ declare_class!(
#[method(windowDidResize:)]
fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:");
// NOTE: WindowEvent::Resized is reported in frameDidChange.
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event();
}
@@ -168,7 +178,7 @@ declare_class!(
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillStartLiveResize:");
let increments = self.ivars().resize_increments.get();
let increments = self.ivars().surface_resize_increments.get();
self.set_resize_increments_inner(increments);
}
@@ -214,10 +224,10 @@ declare_class!(
trace_scope!("windowDidResignKey:");
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// NSWindowDelegate will receive a didResignKey event despite no event
// being received when the modifiers are released. This is because
// being received when the modifiers are released. This is because
// flagsChanged events are received by the NSView instead of the
// NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit
// easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus.
self.view().reset_modifiers();
@@ -435,9 +445,15 @@ declare_class!(
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
// in the future we might want to observe other key paths.
if key_path == Some(ns_string!("effectiveAppearance")) {
let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
let change = change.expect(
"requested a change dictionary in `addObserver`, but none was provided",
);
let old = change
.get(unsafe { NSKeyValueChangeOldKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change
.get(unsafe { NSKeyValueChangeNewKey })
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old;
@@ -482,7 +498,7 @@ impl Drop for WindowDelegate {
}
fn new_window(
app_delegate: &ApplicationDelegate,
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
mtm: MainThreadMarker,
) -> Option<Retained<WinitWindow>> {
@@ -501,7 +517,7 @@ fn new_window(
let scale_factor = NSScreen::mainScreen(mtm)
.map(|screen| screen.backingScaleFactor() as f64)
.unwrap_or(1.0);
let size = match attrs.inner_size {
let size = match attrs.surface_size {
Some(size) => {
let size = size.to_logical(scale_factor);
NSSize::new(size.width, size.height)
@@ -554,9 +570,20 @@ fn new_window(
}
if attrs.platform_specific.fullsize_content_view {
// NOTE: If we decide to add an option to change this at runtime, we must emit a
// `SurfaceResized` event to let applications know that the safe area changed.
//
// An alternative would be to add a `WindowEvent::SafeAreaChanged` event, this could be
// done with an observer on `safeAreaRect` / `contentLayoutRect`, see:
// <https://github.com/rust-windowing/winit/issues/3911>
masks |= NSWindowStyleMask::FullSizeContentView;
}
// NOTE: This should only be created after the application has started launching,
// (`applicationWillFinishLaunching:` at the earliest), otherwise you'll run into very
// confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let window: Option<Retained<WinitWindow>> = unsafe {
msg_send_id![
super(mtm.alloc().set_ivars(())),
@@ -607,6 +634,14 @@ fn new_window(
if attrs.platform_specific.movable_by_window_background {
window.setMovableByWindowBackground(true);
}
if attrs.platform_specific.unified_titlebar {
unsafe {
// The toolbar style is ignored if there is no toolbar, so it is
// necessary to add one.
window.setToolbar(Some(&NSToolbar::new(mtm)));
window.setToolbarStyle(NSWindowToolbarStyle::Unified);
}
}
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
@@ -622,10 +657,10 @@ fn new_window(
}
let view = WinitView::new(
app_delegate,
&window,
app_state,
attrs.platform_specific.accepts_first_mouse,
attrs.platform_specific.option_as_alt,
mtm,
);
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
@@ -647,6 +682,23 @@ fn new_window(
window.setContentView(Some(&view));
window.setInitialFirstResponder(Some(&view));
// Configure the view to send notifications whenever its frame rectangle changes.
//
// We explicitly do this _after_ setting the view as the content view of the window, to
// avoid a resize event when creating the window.
view.setPostsFrameChangedNotifications(true);
// `setPostsFrameChangedNotifications` posts the notification immediately, so register the
// observer _after_, again so that the event isn't triggered initially.
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&view,
sel!(viewFrameDidChangeNotification:),
Some(NSViewFrameDidChangeNotification),
Some(&view),
)
}
if attrs.transparent {
window.setOpaque(false);
// See `set_transparent` for details on why we do this.
@@ -665,23 +717,22 @@ fn new_window(
impl WindowDelegate {
pub(super) fn new(
app_delegate: &ApplicationDelegate,
app_state: &Rc<AppState>,
attrs: WindowAttributes,
mtm: MainThreadMarker,
) -> Result<Retained<Self>, RootOsError> {
let window = new_window(app_delegate, &attrs, mtm)
.ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?;
) -> Result<Retained<Self>, RequestError> {
let window = new_window(app_state, &attrs, mtm)
.ok_or_else(|| os_error!("couldn't create `NSWindow`"))?;
#[cfg(feature = "rwh_06")]
match attrs.parent_window.map(|handle| handle.0) {
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
// SAFETY: Caller ensures the pointer is valid or NULL
// Unwrap is fine, since the pointer comes from `NonNull`.
let parent_view: Retained<NSView> =
unsafe { Retained::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
let parent = parent_view.window().ok_or_else(|| {
os_error!(OsError::CreationError("parent view should be installed in a window"))
})?;
let parent = parent_view
.window()
.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
// SAFETY: We know that there are no parent -> child -> parent cycles since the only
// place in `winit` where we allow making a window a child window is
@@ -694,13 +745,15 @@ impl WindowDelegate {
None => (),
}
let resize_increments =
match attrs.resize_increments.map(|i| i.to_logical(window.backingScaleFactor() as _)) {
Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => {
NSSize::new(width, height)
},
_ => NSSize::new(1., 1.),
};
let surface_resize_increments = match attrs
.surface_resize_increments
.map(|i| i.to_logical(window.backingScaleFactor() as _))
{
Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => {
NSSize::new(width, height)
},
_ => NSSize::new(1., 1.),
};
let scale_factor = window.backingScaleFactor() as _;
@@ -709,11 +762,11 @@ impl WindowDelegate {
}
let delegate = mtm.alloc().set_ivars(State {
app_delegate: app_delegate.retain(),
app_state: Rc::clone(app_state),
window: window.retain(),
previous_position: Cell::new(None),
previous_position: Cell::new(flip_window_screen_coordinates(window.frame())),
previous_scale_factor: Cell::new(scale_factor),
resize_increments: Cell::new(resize_increments),
surface_resize_increments: Cell::new(surface_resize_increments),
decorations: Cell::new(attrs.decorations),
resizable: Cell::new(attrs.resizable),
maximized: Cell::new(attrs.maximized),
@@ -725,15 +778,10 @@ impl WindowDelegate {
standard_frame: Cell::new(None),
is_simple_fullscreen: Cell::new(false),
saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
}
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
// Listen for theme change event.
@@ -753,21 +801,17 @@ impl WindowDelegate {
delegate.set_blur(attrs.blur);
}
if let Some(dim) = attrs.min_inner_size {
delegate.set_min_inner_size(Some(dim));
if let Some(dim) = attrs.min_surface_size {
delegate.set_min_surface_size(Some(dim));
}
if let Some(dim) = attrs.max_inner_size {
delegate.set_max_inner_size(Some(dim));
if let Some(dim) = attrs.max_surface_size {
delegate.set_max_surface_size(Some(dim));
}
delegate.set_window_level(attrs.window_level);
delegate.set_cursor(attrs.cursor);
// XXX Send `Focused(false)` right after creating the window delegate, so we won't
// obscure the real focused events on the startup.
delegate.queue_event(WindowEvent::Focused(false));
// Set fullscreen mode after we setup everything
delegate.set_fullscreen(attrs.fullscreen.map(Into::into));
@@ -807,8 +851,8 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
}
@@ -820,30 +864,29 @@ impl WindowDelegate {
let content_size = LogicalSize::new(content_size.width, content_size.height);
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let new_surface_size = Arc::new(Mutex::new(suggested_size));
self.queue_event(WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
});
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
self.queue_event(WindowEvent::Resized(physical_size));
self.queue_event(WindowEvent::SurfaceResized(physical_size));
}
fn emit_move_event(&self) {
let frame = self.window().frame();
if self.ivars().previous_position.get() == Some(frame.origin) {
let position = flip_window_screen_coordinates(self.window().frame());
if self.ivars().previous_position.get() == position {
return;
}
self.ivars().previous_position.set(Some(frame.origin));
self.ivars().previous_position.set(position);
let position = flip_window_screen_coordinates(frame);
let position =
LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor());
self.queue_event(WindowEvent::Moved(position));
@@ -907,21 +950,34 @@ impl WindowDelegate {
}
pub fn request_redraw(&self) {
self.ivars().app_delegate.queue_redraw(self.window().id());
self.ivars().app_state.queue_redraw(self.window().id());
}
#[inline]
pub fn pre_present_notify(&self) {}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
let position = flip_window_screen_coordinates(self.window().frame());
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let position = flip_window_screen_coordinates(content_rect);
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
pub fn surface_position(&self) -> PhysicalPosition<i32> {
// The calculation here is a bit awkward because we've gotta reconcile the
// different origins (Winit prefers top-left vs. NSWindow's bottom-left),
// and I couldn't find a built-in way to do so.
// The position of the window and the view, both in Winit screen coordinates.
let window_position = flip_window_screen_coordinates(self.window().frame());
let view_position = flip_window_screen_coordinates(
self.window().contentRectForFrameRect(self.window().frame()),
);
// And use that to convert the view position to window coordinates.
let surface_position =
NSPoint::new(view_position.x - window_position.x, view_position.y - window_position.y);
let logical = LogicalPosition::new(surface_position.x, surface_position.y);
logical.to_physical(self.scale_factor())
}
pub fn set_outer_position(&self, position: Position) {
@@ -934,7 +990,7 @@ impl WindowDelegate {
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
pub fn surface_size(&self) -> PhysicalSize<u32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
logical.to_physical(self.scale_factor())
@@ -947,15 +1003,46 @@ impl WindowDelegate {
logical.to_physical(self.scale_factor())
}
pub fn safe_area(&self) -> PhysicalInsets<u32> {
// Only available on macOS 11.0
let insets = if self.view().respondsToSelector(sel!(safeAreaInsets)) {
// Includes NSWindowStyleMask::FullSizeContentView by default, and the notch because
// we've set it up with `additionalSafeAreaInsets`.
unsafe { self.view().safeAreaInsets() }
} else {
// If `safeAreaInsets` is not available, we'll have to do the calculation ourselves.
let window_rect = unsafe {
self.window().convertRectFromScreen(
self.window().contentRectForFrameRect(self.window().frame()),
)
};
// This includes NSWindowStyleMask::FullSizeContentView.
let layout_rect = unsafe { self.window().contentLayoutRect() };
// Calculate the insets from window coordinates in AppKit's coordinate system.
NSEdgeInsets {
top: (window_rect.size.height + window_rect.origin.y)
- (layout_rect.size.height + layout_rect.origin.y),
left: layout_rect.origin.x - window_rect.origin.x,
bottom: layout_rect.origin.y - window_rect.origin.y,
right: (window_rect.size.width + window_rect.origin.x)
- (layout_rect.size.width + layout_rect.origin.x),
}
};
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
insets.to_physical(self.scale_factor())
}
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
pub fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let scale_factor = self.scale_factor();
let size = size.to_logical(scale_factor);
self.window().setContentSize(NSSize::new(size.width, size.height));
None
}
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
pub fn set_min_surface_size(&self, dimensions: Option<Size>) {
let dimensions =
dimensions.unwrap_or(Size::Logical(LogicalSize { width: 0.0, height: 0.0 }));
let min_size = dimensions.to_logical::<CGFloat>(self.scale_factor());
@@ -974,7 +1061,7 @@ impl WindowDelegate {
self.window().setContentSize(current_size);
}
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
pub fn set_max_surface_size(&self, dimensions: Option<Size>) {
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
width: f32::MAX as f64,
height: f32::MAX as f64,
@@ -996,8 +1083,8 @@ impl WindowDelegate {
self.window().setContentSize(current_size);
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
let increments = self.ivars().resize_increments.get();
pub fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
let increments = self.ivars().surface_resize_increments.get();
let (w, h) = (increments.width, increments.height);
if w > 1.0 || h > 1.0 {
Some(LogicalSize::new(w, h).to_physical(self.scale_factor()))
@@ -1006,9 +1093,9 @@ impl WindowDelegate {
}
}
pub fn set_resize_increments(&self, increments: Option<Size>) {
pub fn set_surface_resize_increments(&self, increments: Option<Size>) {
// XXX the resize increments are only used during live resizes.
self.ivars().resize_increments.set(
self.ivars().surface_resize_increments.set(
increments
.map(|increments| {
let logical = increments.to_logical::<f64>(self.scale_factor());
@@ -1113,18 +1200,18 @@ impl WindowDelegate {
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
let associate_mouse_cursor = match mode {
CursorGrabMode::Locked => false,
CursorGrabMode::None => true,
CursorGrabMode::Confined => {
return Err(ExternalError::NotSupported(NotSupportedError::new()))
return Err(NotSupportedError::new("confined cursor is not supported").into())
},
};
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
.map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status))))
.map_err(|status| os_error!(format!("CGError {status}")).into())
}
#[inline]
@@ -1142,43 +1229,42 @@ impl WindowDelegate {
}
#[inline]
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
let physical_window_position = self.inner_position().unwrap();
let scale_factor = self.scale_factor();
let window_position = physical_window_position.to_logical::<CGFloat>(scale_factor);
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(scale_factor);
let point = CGPoint {
x: logical_cursor_position.x + window_position.x,
y: logical_cursor_position.y + window_position.y,
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), RequestError> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
let window_position = flip_window_screen_coordinates(content_rect);
let cursor_position = cursor_position.to_logical::<CGFloat>(self.scale_factor());
let point = core_graphics::display::CGPoint {
x: window_position.x + cursor_position.x,
y: window_position.y + cursor_position.y,
};
CGDisplay::warp_mouse_cursor_position(point)
.map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?;
.map_err(|status| os_error!(format!("CGError {status}")))?;
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
.map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?;
.map_err(|status| os_error!(format!("CGError {status}")))?;
Ok(())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
pub fn drag_window(&self) -> Result<(), RequestError> {
let mtm = MainThreadMarker::from(self);
let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap();
let event =
NSApplication::sharedApplication(mtm).currentEvent().ok_or(RequestError::Ignored)?;
self.window().performWindowDragWithEvent(&event);
Ok(())
}
#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("drag_resize_window is not supported"))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
pub fn set_cursor_hittest(&self, hittest: bool) {
self.window().setIgnoresMouseEvents(!hittest);
Ok(())
}
pub(crate) fn is_zoomed(&self) -> bool {
@@ -1411,7 +1497,7 @@ impl WindowDelegate {
}
match (old_fullscreen, fullscreen) {
(None, Some(_)) => {
(None, Some(fullscreen)) => {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
@@ -1421,6 +1507,17 @@ impl WindowDelegate {
self.set_style_mask(required);
self.ivars().saved_style.set(Some(curr_mask));
}
// In borderless games, we want to disable the dock and menu bar
// by setting the presentation options. We do this here rather than in
// `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
}
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), None) => {
@@ -1598,7 +1695,6 @@ impl WindowDelegate {
Some(monitor)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
let window_handle = rwh_06::AppKitWindowHandle::new({
@@ -1721,12 +1817,15 @@ impl WindowExtMacOS for WindowDelegate {
let screen = self.window().screen().expect("expected screen to be available");
self.window().setFrame_display(screen.frame(), true);
// Configure the safe area rectangle, to ensure that we don't obscure the notch.
if NSScreen::class().responds_to(sel!(safeAreaInsets)) {
unsafe { self.view().setAdditionalSafeAreaInsets(screen.safeAreaInsets()) };
}
// Fullscreen windows can't be resized, minimized, or moved
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
self.window().setMovable(false);
true
} else {
let new_mask = self.saved_style();
self.set_style_mask(new_mask);
@@ -1739,11 +1838,22 @@ impl WindowExtMacOS for WindowDelegate {
app.setPresentationOptions(presentation_opts);
}
if NSScreen::class().responds_to(sel!(safeAreaInsets)) {
unsafe {
self.view().setAdditionalSafeAreaInsets(NSEdgeInsets {
top: 0.0,
left: 0.0,
bottom: 0.0,
right: 0.0,
});
}
}
self.window().setFrame_display(frame, true);
self.window().setMovable(true);
true
}
true
}
#[inline]
@@ -1807,6 +1917,42 @@ impl WindowExtMacOS for WindowDelegate {
fn option_as_alt(&self) -> OptionAsAlt {
self.view().option_as_alt()
}
fn set_borderless_game(&self, borderless_game: bool) {
self.ivars().is_borderless_game.set(borderless_game);
}
fn is_borderless_game(&self) -> bool {
self.ivars().is_borderless_game.get()
}
fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.window();
if unified_titlebar {
let mtm = MainThreadMarker::from(self);
unsafe {
// The toolbar style is ignored if there is no toolbar, so it is
// necessary to add one.
window.setToolbar(Some(&NSToolbar::new(mtm)));
window.setToolbarStyle(NSWindowToolbarStyle::Unified);
}
} else {
unsafe {
window.setToolbar(None);
window.setToolbarStyle(NSWindowToolbarStyle::Automatic);
}
}
}
fn unified_titlebar(&self) -> bool {
let window = self.window();
unsafe {
window.toolbar().is_some() && window.toolbarStyle() == NSWindowToolbarStyle::Unified
}
}
}
const DEFAULT_STANDARD_FRAME: NSRect =

View File

@@ -2,8 +2,9 @@ use std::cell::RefCell;
use std::{fmt, mem};
use crate::application::ApplicationHandler;
use crate::platform_impl::ActiveEventLoop;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure.
#[derive(Default)]
pub(crate) struct EventHandler {
/// This can be in the following states:
@@ -100,6 +101,7 @@ impl EventHandler {
// soundness.
}
#[cfg(target_os = "macos")]
pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
@@ -108,11 +110,7 @@ impl EventHandler {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
event_loop: &ActiveEventLoop,
) {
pub(crate) fn handle(&self, callback: impl FnOnce(&mut dyn ApplicationHandler)) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here,
@@ -121,10 +119,10 @@ impl EventHandler {
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(*user_app, event_loop);
callback(*user_app);
},
Ok(None) => {
// `NSApplication`, our app delegate and this handler are all
// `NSApplication`, our app state and this handler are all
// global state and so it's not impossible that we could get
// an event after the application has exited the `EventLoop`.
tracing::error!("tried to run event handler, but no handler was set");

View File

@@ -2,10 +2,14 @@
#[cfg(target_os = "macos")]
mod appkit;
mod event_handler;
mod notification_center;
#[cfg(not(target_os = "macos"))]
mod uikit;
#[allow(unused_imports)]
#[cfg(target_os = "macos")]
pub use self::appkit::*;
#[allow(unused_imports)]
#[cfg(not(target_os = "macos"))]
pub use self::uikit::*;

View File

@@ -0,0 +1,27 @@
use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
/// Observe the given notification.
///
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
/// give the user full control over those.
pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});
unsafe {
center.addObserverForName_object_queue_usingBlock(
Some(name),
None, // No sender filter
None, // No queue, run on posting thread (i.e. main thread)
&block,
)
}
}

View File

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

View File

@@ -1,12 +1,12 @@
#![deny(unused_results)]
use std::cell::{RefCell, RefMut};
use std::cell::{OnceCell, RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use std::{fmt, mem, ptr};
use std::{mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
@@ -15,20 +15,21 @@ use core_foundation::runloop::{
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use objc2::sel;
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::super::event_handler::EventHandler;
use super::window::WinitUIWindow;
use super::ActiveEventLoop;
use super::{ActiveEventLoop, EventLoopProxy};
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
macro_rules! bug {
($($msg:tt)*) => {
@@ -42,30 +43,34 @@ macro_rules! bug_assert {
};
}
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event, &dyn RootActiveEventLoop)>,
pub(crate) event_loop: ActiveEventLoop,
}
/// Get the global event handler for the application.
///
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
/// is executing.
fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler {
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl fmt::Debug for EventLoopHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopHandler")
.field("handler", &"...")
.field("event_loop", &self.event_loop)
.finish()
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event) {
(self.handler)(event, &self.event_loop)
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept
// of the main thread.
static GLOBAL: StaticMainThreadBound<OnceCell<EventHandler>> =
StaticMainThreadBound(OnceCell::new());
GLOBAL.get(mtm).get_or_init(EventHandler::new)
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event),
Window { window_id: WindowId, event: WindowEvent },
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -76,20 +81,9 @@ pub struct ScaleFactorChanged {
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> {
Success {
handler: EventLoopHandler,
active_control_flow: ControlFlow,
processing_redraws: bool,
},
ReentrancyPrevented {
queued_events: &'a mut Vec<EventWrapper>,
},
}
impl Event {
impl EventWrapper {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. })
}
}
@@ -97,38 +91,20 @@ impl Event {
#[derive(Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
Launching {
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler,
Initial {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
// special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback {
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws {
handler: EventLoopHandler,
active_control_flow: ControlFlow,
},
Waiting {
waiting_handler: EventLoopHandler,
start: Instant,
},
PollFinished {
waiting_handler: EventLoopHandler,
},
PollFinished,
Terminated,
}
@@ -137,7 +113,8 @@ pub(crate) struct AppState {
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
waker: EventLoopWaker,
proxy_wake_up: Arc<AtomicBool>,
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
}
impl AppState {
@@ -147,6 +124,8 @@ impl AppState {
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
#[allow(unknown_lints)] // New lint below
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead.
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
@@ -154,14 +133,11 @@ impl AppState {
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(),
queued_events: Vec::new(),
queued_gpu_redraws: HashSet::new(),
}),
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
proxy_wake_up: Arc::new(AtomicBool::new(false)),
event_loop_proxy: Arc::new(EventLoopProxy::new()),
queued_events: Vec::new(),
});
}
init_guard(&mut guard);
@@ -207,154 +183,61 @@ impl AppState {
}
fn has_launched(&self) -> bool {
!matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. })
!matches!(self.state(), AppStateImpl::Initial { .. })
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
}
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { queued_windows, queued_events, queued_gpu_redraws } => {
(queued_windows, queued_events, queued_gpu_redraws)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_gpu_redraws,
});
}
fn did_finish_launching_transition(
&mut self,
) -> (Vec<Retained<WinitUIWindow>>, Vec<EventWrapper>) {
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching {
queued_windows,
queued_events,
queued_handler,
queued_gpu_redraws,
} => (queued_windows, queued_events, queued_handler, queued_gpu_redraws),
fn did_finish_launching_transition(&mut self) {
let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
(windows, events)
}
fn wakeup_transition(&mut self) -> Option<EventWrapper> {
fn wakeup_transition(&mut self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let (handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => {
(waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)))
let start_cause = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
StartCause::WaitCancelled { start, requested_resume: None }
},
(ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => (
waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: None,
})),
),
(
ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { waiting_handler, start },
) => {
let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
start,
requested_resume,
}))
(ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached { start, requested_resume }
} else {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
requested_resume: Some(requested_resume),
}))
};
(waiting_handler, event)
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
}
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
handler,
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
Some(event)
}
fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> {
// If we're not able to process an event due to recursion or `Init` not having been sent out
// yet, then queue the events up.
match self.state_mut() {
&mut AppStateImpl::Launching { ref mut queued_events, .. }
| &mut AppStateImpl::NotLaunched { ref mut queued_events, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => {
// A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround.
return unsafe {
mem::transmute::<
UserCallbackTransitionResult<'_>,
UserCallbackTransitionResult<'_>,
>(UserCallbackTransitionResult::ReentrancyPrevented {
queued_events,
})
};
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s)
},
}
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = match self
.take_state()
{
AppStateImpl::Launching { .. }
| AppStateImpl::NotLaunched { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(),
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
(handler, queued_gpu_redraws, active_control_flow, false)
},
AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
(handler, Default::default(), active_control_flow, true)
},
AppStateImpl::PollFinished { .. }
| AppStateImpl::Waiting { .. }
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(),
queued_gpu_redraws,
});
UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws }
Some(start_cause)
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
(handler, queued_gpu_redraws, active_control_flow)
let (queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow)
},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingRedraws { handler, active_control_flow });
self.set_state(AppStateImpl::ProcessingRedraws { active_control_flow });
queued_gpu_redraws
}
@@ -362,10 +245,8 @@ impl AppState {
if !self.has_launched() || self.has_terminated() {
return;
}
let (waiting_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
(handler, active_control_flow)
},
let old = match self.take_state() {
AppStateImpl::ProcessingRedraws { active_control_flow } => active_control_flow,
s => bug!("unexpected state {:?}", s),
};
@@ -373,41 +254,41 @@ impl AppState {
match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.set_state(AppStateImpl::Waiting { start });
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
{
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.set_state(AppStateImpl::Waiting { start });
},
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.set_state(AppStateImpl::Waiting { start });
self.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.set_state(AppStateImpl::Waiting { start });
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_handler });
self.set_state(AppStateImpl::PollFinished);
self.waker.start()
},
}
}
fn terminated_transition(&mut self) -> EventLoopHandler {
fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { handler, .. } => handler,
s => bug!("`LoopExiting` happened while not processing events {:?}", s),
AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("terminated while not processing events {:?}", s),
}
}
pub(crate) fn proxy_wake_up(&self) -> Arc<AtomicBool> {
self.proxy_wake_up.clone()
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
@@ -419,33 +300,11 @@ impl AppState {
}
}
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
return queued_windows.push(window.clone())
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::Launching { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated")
},
}
drop(this);
window.makeKeyAndVisible();
}
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::Launching { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::InUserCallback { ref mut queued_gpu_redraws, .. } => {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
let _ = queued_gpu_redraws.insert(window);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
@@ -457,71 +316,36 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<W
}
}
pub(crate) fn will_launch(mtm: MainThreadMarker, queued_handler: EventLoopHandler) {
AppState::get_mut(mtm).will_launch_transition(queued_handler)
pub(crate) fn launch(mtm: MainThreadMarker, app: &mut dyn ApplicationHandler, run: impl FnOnce()) {
get_handler(mtm).set(app, run)
}
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),
};
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
for window in windows {
// Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we
// gotta reset the `rootViewController`.
//
// relevant iOS log:
// ```
// [ApplicationLifecycle] Windows were created before application initialization
// completed. This may result in incorrect visual appearance.
// ```
let screen = window.screen();
let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen);
AppState::get_mut(mtm).did_finish_launching_transition();
let controller = window.rootViewController();
window.setRootViewController(None);
window.setRootViewController(controller.as_deref());
window.makeKeyAndVisible();
}
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = [
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)),
EventWrapper::StaticEvent(Event::CreateSurfaces),
]
.into_iter()
.chain(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
for window in windows {
window.makeKeyAndVisible();
}
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() {
let cause = match this.wakeup_transition() {
None => return,
Some(wakeup_event) => wakeup_event,
Some(cause) => cause,
};
drop(this);
handle_nonuser_event(mtm, wakeup_event)
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
@@ -537,137 +361,76 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
return;
}
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
};
if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events);
return;
}
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
drop(this);
for wrapper in events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handler.handle_event(event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
for event in events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non `RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event)
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
bug_assert!(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws { handler, active_control_flow }
} else {
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow }
});
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event
);
}
handler.handle_event(event)
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
for event in queued_events {
if !processing_redraws && event.is_redraw() {
tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() {
tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}",
event
);
}
handle_wrapped_event(mtm, event);
}
}
}
fn handle_user_events(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let (mut handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
processing_redraws,
} => (handler, active_control_flow, processing_redraws),
};
if processing_redraws {
let this = AppState::get_mut(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
let event_loop_proxy = this.event_loop_proxy().clone();
drop(this);
handler.handle_event(Event::UserWakeUp);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
let queued_events = mem::take(&mut this.queued_events);
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(AppStateImpl::ProcessingEvents {
handler,
queued_gpu_redraws,
active_control_flow,
});
break;
}
drop(this);
for wrapper in queued_events {
match wrapper {
EventWrapper::StaticEvent(event) => handler.handle_event(event),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
for event in queued_events {
handle_wrapped_event(mtm, event);
}
handler.handle_event(Event::UserWakeUp);
if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}
}
}
@@ -684,10 +447,10 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Occluded(occluded),
}));
});
}
}
handle_nonuser_events(mtm, events);
@@ -710,23 +473,37 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested,
})
.map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
.collect();
drop(this);
handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_suspended(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) {
get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
@@ -740,35 +517,41 @@ pub(crate) fn terminated(application: &UIApplication) {
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
events.push(EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::Destroyed,
}));
});
}
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
this.terminated_transition();
drop(this);
handler.handle_event(Event::LoopExiting)
get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm }));
}
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) {
match event {
EventWrapper::Window { window_id, event } => get_handler(mtm)
.handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)),
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event),
}
}
fn handle_hidpi_proxy(mtm: MainThreadMarker, 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 {
let new_surface_size = Arc::new(Mutex::new(suggested_size));
get_handler(mtm).handle(|app| {
app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
},
};
handler.handle_event(event);
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
});
});
let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_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 File

@@ -1,4 +1,3 @@
use std::any::Any;
use std::ffi::{c_char, c_int, c_void};
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
@@ -13,22 +12,29 @@ use core_foundation::runloop::{
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use super::app_delegate::AppDelegate;
use super::app_state::{AppState, EventLoopHandler};
use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError};
use crate::event::Event;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform_impl::Window;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as RootWindow};
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
#[derive(Debug)]
pub(crate) struct ActiveEventLoop {
@@ -36,24 +42,22 @@ pub(crate) struct ActiveEventLoop {
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> crate::event_loop::EventLoopProxy {
let event_loop_proxy = EventLoopProxy::new(AppState::get_mut(self.mtm).proxy_wake_up());
RootEventLoopProxy { event_loop_proxy }
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<RootWindow, OsError> {
let window = Window::new(self, window_attributes)?;
Ok(RootWindow { window })
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_source: CustomCursorSource,
) -> Result<CustomCursor, ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
) -> Result<CustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
@@ -90,21 +94,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
false
}
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
@@ -115,45 +113,28 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::UiKitDisplayHandle::new().into())
}
}
fn map_user_event<'a, A: ApplicationHandler + 'a>(
mut app: A,
proxy_wake_up: Arc<AtomicBool>,
) -> impl FnMut(Event, &dyn RootActiveEventLoop) + 'a {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserWakeUp => {
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
app.proxy_wake_up(window_target);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::CreateSurfaces => app.can_create_surfaces(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
pub struct EventLoop {
mtm: MainThreadMarker,
window_target: ActiveEventLoop,
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
// system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<NSObject>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -178,10 +159,90 @@ impl EventLoop {
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
Ok(EventLoop { mtm, window_target: ActiveEventLoop { mtm } })
let center = unsafe { NSNotificationCenter::defaultCenter() };
let _did_finish_launching_observer = create_observer(
&center,
// `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
app_state::did_finish_launching(mtm);
},
);
let _did_become_active_observer = create_observer(
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm),
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm),
);
let _will_enter_foreground_observer = create_observer(
&center,
// `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false);
},
);
let _did_enter_background_observer = create_observer(
&center,
// `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true);
},
);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app);
},
);
let _did_receive_memory_warning_observer = create_observer(
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm),
);
Ok(EventLoop {
mtm,
window_target: ActiveEventLoop { mtm },
_did_finish_launching_observer,
_did_become_active_observer,
_will_resign_active_observer,
_will_enter_foreground_observer,
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
})
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
pub fn run_app<A: ApplicationHandler>(self, mut app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -191,36 +252,23 @@ impl EventLoop {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(app, AppState::get_mut(self.mtm).proxy_wake_up());
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event, &dyn RootActiveEventLoop)>,
Box<dyn FnMut(Event, &dyn RootActiveEventLoop)>,
>(Box::new(handler))
};
let handler = EventLoopHandler { handler, event_loop: self.window_target };
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
let _ = AppDelegate::class();
extern "C" {
// These functions are in crt_externs.h.
fn _NSGetArgc() -> *mut c_int;
fn _NSGetArgv() -> *mut *mut *mut c_char;
}
unsafe {
app_state::launch(self.mtm, &mut app, || unsafe {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
// We intentionally override neither the application nor the delegate, to allow
// the user to do so themselves!
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
};
None,
);
});
unreachable!()
}
@@ -230,19 +278,13 @@ impl EventLoop {
}
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Clone for EventLoopProxy {
fn clone(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
}
}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
@@ -253,7 +295,7 @@ impl Drop for EventLoopProxy {
}
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
pub(crate) fn new() -> EventLoopProxy {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -277,12 +319,14 @@ impl EventLoopProxy {
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { proxy_wake_up, source }
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);

View File

@@ -1,6 +1,5 @@
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod monitor;
@@ -11,42 +10,16 @@ mod window;
use std::fmt;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
use crate::event::DeviceId as RootDeviceId;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
/// There is no way to detect which device that performed a certain event in
/// UIKit (i.e. you can't differentiate between different external keyboards,
/// or whether it was the main touchscreen, assistive technologies, or some
/// other pointer device that caused a touch event).
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
}
}
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId(usize);
impl FingerId {
pub const fn dummy() -> Self {
FingerId(0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {}

View File

@@ -101,13 +101,20 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
}
}
@@ -121,8 +128,10 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
}
}
@@ -240,3 +249,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
}
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -4,20 +4,25 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_ui_kit::{
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer,
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use tracing::debug;
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use super::{FingerId, DEVICE_ID};
use crate::dpi::PhysicalPosition;
use crate::event::{Event, FingerId as RootFingerId, Force, Touch, TouchPhase, WindowEvent};
use crate::window::{WindowAttributes, WindowId as RootWindowId};
use crate::event::{
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::KeyEventExtra;
use crate::window::WindowAttributes;
pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Retained<UIPinchGestureRecognizer>>>,
@@ -29,6 +34,9 @@ pub struct WinitViewState {
rotation_last_delta: Cell<CGFloat>,
pinch_last_delta: Cell<CGFloat>,
pan_last_delta: Cell<CGPoint>,
primary_finger: Cell<Option<FingerId>>,
fingers: Cell<u8>,
}
declare_class!(
@@ -52,10 +60,10 @@ declare_class!(
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
}),
},
);
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
@@ -65,32 +73,21 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let window = self.window().unwrap();
let window_bounds = window.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
let scale_factor = screen.scale();
let frame = self.frame();
let scale_factor = self.contentScaleFactor() as f64;
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
}
.to_physical(scale_factor as f64);
// If the app is started in landscape, the view frame and window bounds can be mismatched.
// The view frame will be in portrait and the window bounds in landscape. So apply the
// window bounds to the view frame to make it consistent.
let view_frame = self.frame();
if view_frame != window_bounds {
self.setFrame(window_bounds);
width: frame.size.width as f64,
height: frame.size.height as f64,
}
.to_physical(scale_factor);
let window = self.window().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size),
}),
EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::SurfaceResized(size),
},
);
}
@@ -119,15 +116,12 @@ declare_class!(
"invalid scale_factor set on UIView",
);
let scale_factor = scale_factor as f64;
let bounds = self.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let frame = self.frame();
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
width: frame.size.width as f64,
height: frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
let window_id = window.id();
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(
@@ -137,15 +131,21 @@ declare_class!(
suggested_size: size.to_physical(scale_factor),
},
))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
.chain(std::iter::once(EventWrapper::Window {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)),
},
))),
)),
);
}
#[method(safeAreaInsetsDidChange)]
fn safe_area_changed(&self) {
debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay();
}
#[method(touchesBegan:withEvent:)]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches)
@@ -188,17 +188,17 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale())
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::PinchGesture {
device_id: DEVICE_ID,
device_id: None,
delta: delta as f64,
phase,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -209,12 +209,12 @@ declare_class!(
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
device_id: None,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -247,18 +247,18 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation())
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
// Make delta negative to match macos, convert to degrees
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RotationGesture {
device_id: DEVICE_ID,
device_id: None,
delta: -delta.to_degrees() as _,
phase,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
@@ -298,22 +298,27 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}
state => panic!("unexpected recognizer state: {:?}", state),
state => panic!("unexpected recognizer state: {state:?}"),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
let gesture_event = EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::PanGesture {
device_id: DEVICE_ID,
device_id: None,
delta: PhysicalPosition::new(dx as _, dy as _),
phase,
},
});
};
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(canBecomeFirstResponder)]
fn can_become_first_responder(&self) -> bool {
true
}
}
unsafe impl NSObjectProtocol for WinitView {}
@@ -324,6 +329,26 @@ declare_class!(
true
}
}
unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UIKeyInput for WinitView {
#[method(hasText)]
fn has_text(&self) -> bool {
true
}
#[method(insertText:)]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
#[method(deleteBackward)]
fn delete_backward(&self) {
self.handle_delete_backward()
}
}
);
impl WinitView {
@@ -341,6 +366,9 @@ impl WinitView {
rotation_last_delta: Cell::new(0.0),
pinch_last_delta: Cell::new(0.0),
pan_last_delta: Cell::new(CGPoint { x: 0.0, y: 0.0 }),
primary_finger: Cell::new(None),
fingers: Cell::new(0),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
@@ -454,25 +482,18 @@ impl WinitView {
for touch in touches {
let logical_location = touch.locationInView(None);
let touch_type = touch.r#type();
let force = if os_supports_force {
let force = if let UITouchType::Pencil = touch_type {
None
} else if os_supports_force {
let trait_collection = 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
|| touch_type == UITouchType::Pencil
{
if touch_capability == UIForceTouchCapability::Available {
let force = touch.force();
let max_possible_force = touch.maximumPossibleForce();
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
let angle = touch.altitudeAngle();
Some(angle as _)
} else {
None
};
Some(Force::Calibrated {
force: force as _,
max_possible_force: max_possible_force as _,
altitude_angle,
})
} else {
None
@@ -482,34 +503,195 @@ impl WinitView {
};
let touch_id = touch as *const UITouch as usize;
let phase = touch.phase();
let phase = match phase {
UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved,
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {phase:?}"),
};
let physical_location = {
let position = {
let scale_factor = self.contentScaleFactor();
PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _),
scale_factor as f64,
)
};
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch {
device_id: DEVICE_ID,
finger_id: RootFingerId(FingerId(touch_id)),
location: physical_location,
force,
phase,
}),
}));
let window_id = window.id();
let finger_id = FingerId::from_raw(touch_id);
let ivars = self.ivars();
match phase {
UITouchPhase::Began => {
let primary = if let UITouchType::Pencil = touch_type {
true
} else {
ivars.fingers.set(ivars.fingers.get() + 1);
// Keep the primary finger around until we clear all the fingers to
// recognize it when user briefly removes it.
match ivars.primary_finger.get() {
Some(primary_id) => primary_id == finger_id,
None => {
debug_assert_eq!(
ivars.fingers.get(),
1,
"number of fingers were not counted correctly"
);
ivars.primary_finger.set(Some(finger_id));
true
},
}
};
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerEntered {
device_id: None,
primary,
position,
kind: if let UITouchType::Pencil = touch_type {
PointerKind::Unknown
} else {
PointerKind::Touch(finger_id)
},
},
});
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
primary,
state: ElementState::Pressed,
position,
button: if let UITouchType::Pencil = touch_type {
ButtonSource::Unknown(0)
} else {
ButtonSource::Touch { finger_id, force }
},
},
});
},
UITouchPhase::Moved => {
let (primary, source) = if let UITouchType::Pencil = touch_type {
(true, PointerSource::Unknown)
} else {
(ivars.primary_finger.get().unwrap() == finger_id, PointerSource::Touch {
finger_id,
force,
})
};
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerMoved {
device_id: None,
primary,
position,
source,
},
});
},
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended | UITouchPhase::Cancelled => {
let primary = if let UITouchType::Pencil = touch_type {
true
} else {
ivars.fingers.set(ivars.fingers.get() - 1);
let primary = ivars.primary_finger.get().unwrap() == finger_id;
if ivars.fingers.get() == 0 {
ivars.primary_finger.set(None);
}
primary
};
if let UITouchPhase::Ended = phase {
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerButton {
device_id: None,
primary,
state: ElementState::Released,
position,
button: if let UITouchType::Pencil = touch_type {
ButtonSource::Unknown(0)
} else {
ButtonSource::Touch { finger_id, force }
},
},
});
}
touch_events.push(EventWrapper::Window {
window_id,
event: WindowEvent::PointerLeft {
device_id: None,
primary,
position: Some(position),
kind: if let UITouchType::Pencil = touch_type {
PointerKind::Unknown
} else {
PointerKind::Touch(finger_id)
},
},
});
},
_ => panic!("unexpected touch phase: {phase:?}"),
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, touch_events);
}
fn handle_insert_text(&self, text: &NSString) {
let window = self.window().unwrap();
let window_id = window.id();
let mtm = MainThreadMarker::new().unwrap();
// send individual events for each character
app_state::handle_nonuser_events(
mtm,
text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
},
})
}),
);
}
fn handle_delete_backward(&self) {
let window = self.window().unwrap();
let window_id = window.id();
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window {
window_id,
event: WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
},
is_synthetic: false,
},
}),
);
}
}

View File

@@ -3,14 +3,13 @@
use std::collections::VecDeque;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, NSObject};
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::{
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObjectProtocol,
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObject, NSObjectProtocol,
};
use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
UIViewController, UIWindow,
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use tracing::{debug, warn};
@@ -19,14 +18,18 @@ use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Event, WindowEvent};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::WindowEvent;
use crate::icon::Icon;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
declare_class!(
@@ -48,10 +51,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
EventWrapper::Window {
window_id: self.id(),
event: WindowEvent::Focused(true),
}),
},
);
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
}
@@ -61,10 +64,10 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()),
EventWrapper::Window {
window_id: self.id(),
event: WindowEvent::Focused(false),
}),
},
);
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
}
@@ -78,6 +81,11 @@ impl WinitUIWindow {
frame: CGRect,
view_controller: &UIViewController,
) -> Retained<Self> {
// NOTE: This should only be created after the application has started launching,
// (`application:willFinishLaunchingWithOptions:` at the earliest), otherwise you'll run
// into very confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
@@ -100,7 +108,7 @@ impl WinitUIWindow {
}
pub(crate) fn id(&self) -> WindowId {
(self as *const Self as usize as u64).into()
WindowId::from_raw(self as *const Self as usize)
}
}
@@ -153,20 +161,19 @@ impl Inner {
pub fn pre_present_notify(&self) {}
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let safe_area = self.safe_area_screen_space();
pub fn surface_position(&self) -> PhysicalPosition<i32> {
let view_position = self.view.frame().origin;
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))
unsafe { self.window.convertPoint_fromView(view_position, Some(&self.view)) };
let position = LogicalPosition::new(position.x, position.y);
position.to_physical(self.scale_factor())
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
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))
Ok(position.to_physical(self.scale_factor()))
}
pub fn set_outer_position(&self, physical_position: Position) {
@@ -181,45 +188,52 @@ impl Inner {
self.window.setBounds(bounds);
}
pub fn inner_size(&self) -> PhysicalSize<u32> {
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 surface_size(&self) -> PhysicalSize<u32> {
let frame = self.view.frame();
let size = LogicalSize::new(frame.size.width, frame.size.height);
size.to_physical(self.scale_factor())
}
pub fn outer_size(&self) -> PhysicalSize<u32> {
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,
let frame = self.window.frame();
let size = LogicalSize::new(frame.size.width, frame.size.height);
size.to_physical(self.scale_factor())
}
pub fn request_surface_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.surface_size())
}
pub fn safe_area(&self) -> PhysicalInsets<u32> {
// Only available on iOS 11.0
let insets = if app_state::os_capabilities().safe_area {
self.view.safeAreaInsets()
} else {
// Assume the status bar frame is the only thing that obscures the view
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
#[allow(deprecated)]
let status_bar_frame = app.statusBarFrame();
UIEdgeInsets { top: status_bar_frame.size.height, left: 0.0, bottom: 0.0, right: 0.0 }
};
size.to_physical(scale_factor)
let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right);
insets.to_physical(self.scale_factor())
}
pub fn request_inner_size(&self, _size: Size) -> Option<PhysicalSize<u32>> {
Some(self.inner_size())
pub fn set_min_surface_size(&self, _dimensions: Option<Size>) {
warn!("`Window::set_min_surface_size` is ignored on iOS")
}
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
warn!("`Window::set_min_inner_size` is ignored on iOS")
pub fn set_max_surface_size(&self, _dimensions: Option<Size>) {
warn!("`Window::set_max_surface_size` is ignored on iOS")
}
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
pub fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`Window::set_resize_increments` is ignored on iOS")
pub fn set_surface_resize_increments(&self, _increments: Option<Size>) {
warn!("`Window::set_surface_resize_increments` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
@@ -250,31 +264,31 @@ impl Inner {
debug!("`Window::set_cursor` ignored on iOS")
}
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_position(&self, _position: Position) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("set_cursor_position is not supported"))
}
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("set_cursor_grab is not supported"))
}
pub fn set_cursor_visible(&self, _visible: bool) {
debug!("`Window::set_cursor_visible` is ignored on iOS")
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn drag_window(&self) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("drag_window is not supported"))
}
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("drag_resize_window is not supported"))
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), NotSupportedError> {
Err(NotSupportedError::new("set_cursor_hittest is not supported"))
}
pub fn set_minimized(&self, _minimized: bool) {
@@ -365,12 +379,24 @@ impl Inner {
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
/// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`,
/// requesting focus for the [WinitView]. Since [WinitView] implements
/// [objc2_ui_kit::UIKeyInput], the keyboard will be shown.
/// <https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder>
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
unsafe {
self.view.becomeFirstResponder();
}
} else {
unsafe {
self.view.resignFirstResponder();
}
}
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
warn!("`Window::set_ime_purpose` is ignored on iOS")
}
pub fn focus_window(&self) {
@@ -403,7 +429,6 @@ impl Inner {
self.window.id()
}
#[cfg(feature = "rwh_06")]
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
let mut window_handle = rwh_06::UiKitWindowHandle::new({
let ui_view = Retained::as_ptr(&self.view) as _;
@@ -448,14 +473,14 @@ impl Window {
pub(crate) fn new(
event_loop: &ActiveEventLoop,
window_attributes: WindowAttributes,
) -> Result<Window, RootOsError> {
) -> Result<Window, RequestError> {
let mtm = event_loop.mtm;
if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
if window_attributes.min_surface_size.is_some() {
warn!("`WindowAttributes::min_surface_size` is ignored on iOS");
}
if window_attributes.max_inner_size.is_some() {
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
if window_attributes.max_surface_size.is_some() {
warn!("`WindowAttributes::max_surface_size` is ignored on iOS");
}
// TODO: transparency, visible
@@ -471,7 +496,7 @@ impl Window {
let screen_bounds = screen.bounds();
let frame = match window_attributes.inner_size {
let frame = match window_attributes.surface_size {
Some(dim) => {
let scale_factor = screen.scale();
let size = dim.to_logical::<f64>(scale_factor as f64);
@@ -490,53 +515,16 @@ impl Window {
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
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
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds = view.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = LogicalSize {
width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64,
};
let window_id = RootWindowId(window.id());
app_state::handle_nonuser_events(
mtm,
std::iter::once(EventWrapper::ScaleFactorChanged(app_state::ScaleFactorChanged {
window: window.clone(),
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)),
},
))),
);
}
window.makeKeyAndVisible();
let inner = Inner { window, view_controller, view, gl_or_metal_backed };
Ok(Window { 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| f(inner))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
@@ -548,7 +536,6 @@ impl Window {
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
@@ -557,6 +544,265 @@ impl Window {
}
}
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
fn scale_factor(&self) -> f64 {
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
}
fn request_redraw(&self) {
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
}
fn pre_present_notify(&self) {
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
}
fn reset_dead_keys(&self) {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
fn surface_position(&self) -> PhysicalPosition<i32> {
self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
}
fn surface_size(&self) -> PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.surface_size())
}
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
}
fn outer_size(&self) -> PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
fn safe_area(&self) -> PhysicalInsets<u32> {
self.maybe_wait_on_main(|delegate| delegate.safe_area())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
}
fn set_title(&self, title: &str) {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
fn set_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
}
fn is_visible(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_visible())
}
fn set_resizable(&self, resizable: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
}
fn is_resizable(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
}
fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
}
fn enabled_buttons(&self) -> WindowButtons {
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
}
fn set_minimized(&self, minimized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
}
fn is_minimized(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
}
fn set_maximized(&self, maximized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
}
fn is_maximized(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
}
fn set_fullscreen(&self, fullscreen: Option<crate::window::Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
}
fn fullscreen(&self) -> Option<crate::window::Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
}
fn set_decorations(&self, decorations: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
}
fn is_decorated(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
}
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
}
fn focus_window(&self) {
self.maybe_wait_on_main(|delegate| delegate.focus_window());
}
fn has_focus(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.has_focus())
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
}
fn set_theme(&self, theme: Option<Theme>) {
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
}
fn theme(&self) -> Option<Theme> {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
fn title(&self) -> String {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))?)
}
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))?)
}
fn set_cursor_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
}
fn drag_window(&self) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_window())?)
}
fn drag_resize_window(
&self,
direction: crate::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
fn show_window_menu(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest))?)
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
// WindowExtIOS
impl Inner {
pub fn set_scale_factor(&self, scale_factor: f64) {
@@ -619,7 +865,7 @@ impl Inner {
impl Inner {
fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(self.window.bounds())
self.rect_to_screen_space(self.window.frame())
}
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
@@ -631,75 +877,6 @@ impl Inner {
let screen_space = self.window.screen().coordinateSpace();
self.window.convertRect_fromCoordinateSpace(rect, &screen_space)
}
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();
let safe_bounds = CGRect {
origin: CGPoint {
x: bounds.origin.x + safe_area.left,
y: bounds.origin.y + safe_area.top,
},
size: CGSize {
width: bounds.size.width - safe_area.left - safe_area.right,
height: bounds.size.height - safe_area.top - safe_area.bottom,
},
};
self.rect_to_screen_space(safe_bounds)
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = {
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
#[allow(deprecated)]
app.statusBarFrame()
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height)
} else {
let y = status_bar_frame.size.height;
let height = screen_frame.size.height
- (status_bar_frame.size.height - screen_frame.origin.y);
(y, height)
};
CGRect {
origin: CGPoint { x: screen_frame.origin.x, y },
size: CGSize { width: screen_frame.size.width, height },
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId {
window: *mut WinitUIWindow,
}
impl WindowId {
pub const fn dummy() -> Self {
WindowId { window: std::ptr::null_mut() }
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.window as u64
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self { window: raw_id as _ }
}
}
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl From<&AnyObject> for WindowId {
fn from(window: &AnyObject) -> WindowId {
WindowId { window: window as *const _ as _ }
}
}
#[derive(Clone, Debug, Default, PartialEq)]

View File

@@ -35,6 +35,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes,
// I can only hope they agree on what the keycodes mean.
//
// The mapping here is heavily influenced by Firefox' source:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
//
// 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.
@@ -166,23 +169,23 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
125 => KeyCode::SuperLeft,
126 => KeyCode::SuperRight,
127 => KeyCode::ContextMenu,
// 128 => KeyCode::STOP,
// 129 => KeyCode::AGAIN,
// 130 => KeyCode::PROPS,
// 131 => KeyCode::UNDO,
// 132 => KeyCode::FRONT,
// 133 => KeyCode::COPY,
// 134 => KeyCode::OPEN,
// 135 => KeyCode::PASTE,
// 136 => KeyCode::FIND,
// 137 => KeyCode::CUT,
// 138 => KeyCode::HELP,
128 => KeyCode::BrowserStop,
129 => KeyCode::Again,
130 => KeyCode::Props,
131 => KeyCode::Undo,
132 => KeyCode::Select, // FRONT
133 => KeyCode::Copy,
134 => KeyCode::Open,
135 => KeyCode::Paste,
136 => KeyCode::Find,
137 => KeyCode::Cut,
138 => KeyCode::Help,
// 139 => KeyCode::MENU,
// 140 => KeyCode::CALC,
140 => KeyCode::LaunchApp2, // CALC
// 141 => KeyCode::SETUP,
// 142 => KeyCode::SLEEP,
// 143 => KeyCode::WAKEUP,
// 144 => KeyCode::FILE,
143 => KeyCode::WakeUp,
144 => KeyCode::LaunchApp1, // FILE
// 145 => KeyCode::SENDFILE,
// 146 => KeyCode::DELETEFILE,
// 147 => KeyCode::XFER,
@@ -193,13 +196,13 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 152 => KeyCode::COFFEE,
// 153 => KeyCode::ROTATE_DISPLAY,
// 154 => KeyCode::CYCLEWINDOWS,
// 155 => KeyCode::MAIL,
// 156 => KeyCode::BOOKMARKS,
155 => KeyCode::LaunchMail,
156 => KeyCode::BrowserFavorites, // BOOKMARKS
// 157 => KeyCode::COMPUTER,
// 158 => KeyCode::BACK,
// 159 => KeyCode::FORWARD,
158 => KeyCode::BrowserBack,
159 => KeyCode::BrowserForward,
// 160 => KeyCode::CLOSECD,
// 161 => KeyCode::EJECTCD,
161 => KeyCode::Eject, // EJECTCD
// 162 => KeyCode::EJECTCLOSECD,
163 => KeyCode::MediaTrackNext,
164 => KeyCode::MediaPlayPause,
@@ -209,9 +212,9 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 168 => KeyCode::REWIND,
// 169 => KeyCode::PHONE,
// 170 => KeyCode::ISO,
// 171 => KeyCode::CONFIG,
// 172 => KeyCode::HOMEPAGE,
// 173 => KeyCode::REFRESH,
171 => KeyCode::MediaSelect, // CONFIG
172 => KeyCode::BrowserHome,
173 => KeyCode::BrowserRefresh,
// 174 => KeyCode::EXIT,
// 175 => KeyCode::MOVE,
// 176 => KeyCode::EDIT,
@@ -250,7 +253,7 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// 214 => KeyCode::QUESTION,
// 215 => KeyCode::EMAIL,
// 216 => KeyCode::CHAT,
// 217 => KeyCode::SEARCH,
217 => KeyCode::BrowserSearch,
// 218 => KeyCode::CONNECT,
// 219 => KeyCode::FINANCE,
// 220 => KeyCode::SPORT,
@@ -419,10 +422,32 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::SuperLeft => Some(125),
KeyCode::SuperRight => Some(126),
KeyCode::ContextMenu => Some(127),
KeyCode::BrowserStop => Some(128),
KeyCode::Again => Some(129),
KeyCode::Props => Some(130),
KeyCode::Undo => Some(131),
KeyCode::Select => Some(132),
KeyCode::Copy => Some(133),
KeyCode::Open => Some(134),
KeyCode::Paste => Some(135),
KeyCode::Find => Some(136),
KeyCode::Cut => Some(137),
KeyCode::Help => Some(138),
KeyCode::LaunchApp2 => Some(140),
KeyCode::WakeUp => Some(143),
KeyCode::LaunchApp1 => Some(144),
KeyCode::LaunchMail => Some(155),
KeyCode::BrowserFavorites => Some(156),
KeyCode::BrowserBack => Some(158),
KeyCode::BrowserForward => Some(159),
KeyCode::Eject => Some(161),
KeyCode::MediaTrackNext => Some(163),
KeyCode::MediaPlayPause => Some(164),
KeyCode::MediaTrackPrevious => Some(165),
KeyCode::MediaStop => Some(166),
KeyCode::MediaSelect => Some(171),
KeyCode::BrowserHome => Some(172),
KeyCode::BrowserRefresh => Some(173),
KeyCode::F13 => Some(183),
KeyCode::F14 => Some(184),
KeyCode::F15 => Some(185),
@@ -435,6 +460,7 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
KeyCode::F22 => Some(192),
KeyCode::F23 => Some(193),
KeyCode::F24 => Some(194),
KeyCode::BrowserSearch => Some(217),
_ => None,
}
}
@@ -638,7 +664,7 @@ pub fn keysym_to_key(keysym: u32) -> Key {
// 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_Left => NamedKey::IsoFastPointerLeft,
// keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight,
// keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp,
// keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown,

View File

@@ -183,7 +183,7 @@ pub struct KeyContext<'a> {
scratch_buffer: &'a mut Vec<u8>,
}
impl<'a> KeyContext<'a> {
impl KeyContext<'_> {
pub fn process_key_event(
&mut self,
keycode: u32,

View File

@@ -3,38 +3,33 @@
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::collections::VecDeque;
use std::env;
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::Arc;
use std::time::Duration;
use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
use smol_str::SmolStr;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
#[cfg(x11_platform)]
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use self::x11::{XConnection, XError, XNotSupported};
use crate::application::ApplicationHandler;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError};
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
use crate::icon::Icon;
#[cfg(x11_platform)]
use crate::dpi::Size;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::{EventLoopError, NotSupportedError};
use crate::event_loop::ActiveEventLoop;
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
pub(crate) use crate::platform_impl::Fullscreen;
#[cfg(x11_platform)]
use crate::utils::Lazy;
use crate::window::{
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowButtons, WindowLevel,
};
use crate::window::ActivationToken;
pub(crate) mod common;
#[cfg(wayland_platform)]
@@ -112,89 +107,6 @@ impl Default for PlatformSpecificWindowAttributes {
pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
#[derive(Debug, Clone)]
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
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) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
}
}
}
pub(crate) enum Window {
#[cfg(x11_platform)]
X(x11::Window),
#[cfg(wayland_platform)]
Wayland(wayland::Window),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(u64);
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id)
}
}
impl WindowId {
pub const fn dummy() -> Self {
Self(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeviceId {
#[cfg(x11_platform)]
X(x11::DeviceId),
#[cfg(wayland_platform)]
Wayland(wayland::DeviceId),
}
impl DeviceId {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FingerId {
#[cfg(x11_platform)]
X(x11::FingerId),
#[cfg(wayland_platform)]
Wayland(wayland::FingerId),
}
impl FingerId {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return FingerId::Wayland(wayland::FingerId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return FingerId::X(x11::FingerId::dummy());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum MonitorHandle {
#[cfg(x11_platform)]
@@ -293,316 +205,6 @@ impl VideoModeHandle {
}
}
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 {
x11_or_wayland!(match self; Window(w) => w.id())
}
#[inline]
pub fn set_title(&self, title: &str) {
x11_or_wayland!(match self; Window(w) => w.set_title(title));
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
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))
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_visible())
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.outer_position())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
x11_or_wayland!(match self; Window(w) => w.inner_position())
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
x11_or_wayland!(match self; Window(w) => w.set_outer_position(position))
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; Window(w) => w.inner_size())
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
x11_or_wayland!(match self; Window(w) => w.outer_size())
}
#[inline]
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]
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions))
}
#[inline]
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.resize_increments())
}
#[inline]
pub fn set_resize_increments(&self, increments: Option<Size>) {
x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments))
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
}
#[inline]
pub fn is_resizable(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_resizable())
}
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons))
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
x11_or_wayland!(match self; Window(w) => w.enabled_buttons())
}
#[inline]
pub fn set_cursor(&self, cursor: Cursor) {
x11_or_wayland!(match self; Window(w) => w.set_cursor(cursor))
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
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))
}
#[inline]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor())
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position))
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
}
#[inline]
pub fn is_maximized(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_maximized())
}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
x11_or_wayland!(match self; Window(w) => w.is_minimized())
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
x11_or_wayland!(match self; Window(w) => w.fullscreen())
}
#[inline]
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
}
#[inline]
pub fn is_decorated(&self) -> bool {
x11_or_wayland!(match self; Window(w) => w.is_decorated())
}
#[inline]
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>) {
x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner)))
}
#[inline]
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size))
}
#[inline]
pub fn reset_dead_keys(&self) {
common::xkb::reset_dead_keys()
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
}
#[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
}
#[inline]
pub fn focus_window(&self) {
x11_or_wayland!(match self; Window(w) => w.focus_window())
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type))
}
#[inline]
pub fn request_redraw(&self) {
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> {
Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle))
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match self {
#[cfg(x11_platform)]
Window::X(ref window) => {
window.available_monitors().into_iter().map(MonitorHandle::X).collect()
},
#[cfg(wayland_platform)]
Window::Wayland(ref window) => {
window.available_monitors().into_iter().map(MonitorHandle::Wayland).collect()
},
}
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle))
}
#[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]
pub fn set_theme(&self, theme: Option<Theme>) {
x11_or_wayland!(match self; Window(window) => window.set_theme(theme))
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
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())
}
pub fn title(&self) -> String {
x11_or_wayland!(match self; Window(window) => window.title())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
@@ -619,18 +221,18 @@ pub(crate) enum PlatformCustomCursor {
/// Hooks for X11 errors.
#[cfg(x11_platform)]
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap();
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
@@ -675,14 +277,6 @@ pub enum EventLoop {
X(x11::EventLoop),
}
#[derive(Clone)]
pub enum EventLoopProxy {
#[cfg(x11_platform)]
X(x11::EventLoopProxy),
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy),
}
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
@@ -692,8 +286,8 @@ impl EventLoop {
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you absolutely need to create an \
EventLoop on a different thread, you can use the \
`EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
functions."
`EventLoopBuilderExtX11::with_any_thread` or \
`EventLoopBuilderExtWayland::with_any_thread` functions."
);
}
@@ -728,7 +322,7 @@ impl EventLoop {
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
return Err(NotSupportedError::new(msg).into());
},
};
@@ -748,9 +342,9 @@ impl EventLoop {
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
Ok(xconn) => xconn.clone(),
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
Err(err) => return Err(os_error!(err.clone()).into()),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -802,65 +396,6 @@ impl AsRawFd for EventLoop {
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum OwnedDisplayHandle {
#[cfg(x11_platform)]
X(Arc<XConnection>),
#[cfg(wayland_platform)]
Wayland(wayland_client::Connection),
}
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
use std::ptr::NonNull;
match self {
#[cfg(x11_platform)]
Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
NonNull::new(xconn.display.cast()),
xconn.default_screen_index() as _,
)
.into()),
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
use sctk::reexports::client::Proxy;
Ok(rwh_06::WaylandDisplayHandle::new(
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
)
.into())
},
}
}
}
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
#[cfg(x11_platform)]
(Self::X(this), Self::X(other)) => Arc::as_ptr(this).eq(&Arc::as_ptr(other)),
#[cfg(wayland_platform)]
(Self::Wayland(this), Self::Wayland(other)) => this.eq(other),
#[cfg(all(x11_platform, wayland_platform))]
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
/// 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`)

View File

@@ -1,6 +1,5 @@
//! The event-loop routines.
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::mem;
@@ -9,30 +8,33 @@ use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, ExternalError, OsError as RootOsError};
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::error::{EventLoopError, OsError, RequestError};
use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::{OsError, PlatformCustomCursor};
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
mod proxy;
pub mod sink;
pub use proxy::EventLoopProxy;
use proxy::EventLoopProxy;
use sink::EventSink;
use super::state::{WindowCompositorUpdate, WinitState};
use super::window::state::FrameCallbackState;
use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
use super::{logical_to_physical_rounded, WindowId};
pub use crate::event_loop::EventLoopProxy as CoreEventLoopProxy;
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
@@ -50,10 +52,10 @@ pub struct EventLoop {
wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
connection: Connection,
handle: Arc<OwnedDisplayHandle>,
/// Event loop window target.
pub(crate) active_event_loop: ActiveEventLoop,
active_event_loop: ActiveEventLoop,
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
@@ -62,27 +64,20 @@ pub struct EventLoop {
impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
};
}
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let connection = Connection::connect_to_env().map_err(|err| os_error!(err))?;
let (globals, mut event_queue) =
map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?;
globals::registry_queue_init(&connection).map_err(|err| os_error!(err))?;
let queue_handle = event_queue.handle();
let event_loop =
map_err!(calloop::EventLoop::<WinitState>::try_new(), WaylandError::Calloop)?;
calloop::EventLoop::<WinitState>::try_new().map_err(|err| os_error!(err))?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?;
event_queue.roundtrip(&mut winit_state).map_err(|err| os_error!(err))?;
// Register Wayland source.
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
@@ -98,48 +93,43 @@ impl EventLoop {
result
});
map_err!(
event_loop.handle().register_dispatcher(wayland_dispatcher.clone()),
WaylandError::Calloop
)?;
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())
.map_err(|err| os_error!(err))?;
// Setup the user proxy.
let (ping, ping_source) = calloop::ping::make_ping().unwrap();
let result = event_loop
event_loop
.handle()
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true;
winit_state.proxy_wake_up = true;
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
.map_err(|err| os_error!(err))?;
// An event's loop awakener to wake up for window events from winit's windows.
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 (event_loop_awakener, event_loop_awakener_source) =
calloop::ping::make_ping().map_err(|err| os_error!(err))?;
let result = event_loop
event_loop
.handle()
.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)?;
.map_err(|err| os_error!(err))?;
let handle = Arc::new(OwnedDisplayHandle::new(connection));
let active_event_loop = ActiveEventLoop {
connection: connection.clone(),
handle: handle.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
event_loop_awakener,
event_loop_proxy: EventLoopProxy::new(ping),
event_loop_proxy: EventLoopProxy::new(ping).into(),
queue_handle,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
state: RefCell::new(winit_state),
wayland_callback: Default::default(),
};
let event_loop = Self {
@@ -147,7 +137,7 @@ impl EventLoop {
compositor_updates: Vec::new(),
buffer_sink: EventSink::default(),
window_ids: Vec::new(),
connection,
handle,
wayland_dispatcher,
event_loop,
active_event_loop,
@@ -241,7 +231,7 @@ impl EventLoop {
//
// 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() {
if self.handle.connection.flush().is_err() {
self.set_exit_code(1);
return;
}
@@ -298,10 +288,6 @@ impl EventLoop {
app.new_events(&self.active_event_loop, cause);
if let Some(callback) = self.active_event_loop.wayland_callback.get() {
callback(app);
}
// NB: For consistency all platforms must call `can_create_surfaces` even though Wayland
// applications don't themselves have a formal surface destroy/create lifecycle.
if cause == StartCause::Init {
@@ -323,24 +309,23 @@ impl EventLoop {
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
let size = logical_to_physical_rounded(window.surface_size(), scale_factor);
(size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
let root_window_id = crate::window::WindowId(window_id);
let new_surface_size = Arc::new(Mutex::new(physical_size));
let event = WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)),
};
app.window_event(&self.active_event_loop, root_window_id, event);
app.window_event(&self.active_event_loop, window_id, event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
let physical_size = *new_surface_size.lock().unwrap();
drop(new_surface_size);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
@@ -350,7 +335,7 @@ impl EventLoop {
let new_logical_size: LogicalSize<f64> =
physical_size.to_logical(scale_factor);
window.request_inner_size(new_logical_size.into());
window.request_surface_size(new_logical_size.into());
});
// Make it queue resize.
@@ -366,7 +351,7 @@ impl EventLoop {
let window = windows.get(&window_id).unwrap().lock().unwrap();
let scale_factor = window.scale_factor();
let size = logical_to_physical_rounded(window.inner_size(), scale_factor);
let size = logical_to_physical_rounded(window.surface_size(), scale_factor);
// Mark the window as needed a redraw.
state
@@ -380,13 +365,11 @@ impl EventLoop {
size
});
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::Resized(physical_size);
let event = WindowEvent::SurfaceResized(physical_size);
app.window_event(&self.active_event_loop, window_id, event);
}
if compositor_update.close_window {
let window_id = crate::window::WindowId(window_id);
app.window_event(&self.active_event_loop, window_id, WindowEvent::CloseRequested);
}
}
@@ -456,8 +439,7 @@ impl EventLoop {
});
if let Some(event) = event {
let window_id = crate::window::WindowId(*window_id);
app.window_event(&self.active_event_loop, window_id, event);
app.window_event(&self.active_event_loop, *window_id, event);
}
}
@@ -523,14 +505,12 @@ impl EventLoop {
})
}
fn roundtrip(&mut self) -> Result<usize, RootOsError> {
fn roundtrip(&mut self) -> Result<usize, OsError> {
let state = &mut self.active_event_loop.state.get_mut();
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))))
})
event_queue.roundtrip(state).map_err(|err| os_error!(err))
}
fn control_flow(&self) -> ControlFlow {
@@ -564,7 +544,7 @@ impl AsRawFd for EventLoop {
pub struct ActiveEventLoop {
/// Event loop proxy
event_loop_proxy: EventLoopProxy,
event_loop_proxy: CoreEventLoopProxy,
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,
@@ -585,19 +565,13 @@ pub struct ActiveEventLoop {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
pub connection: Connection,
pub wayland_callback: Cell<Option<fn(&mut dyn ApplicationHandler)>>,
/// Handle for the underlying event loop.
pub handle: Arc<OwnedDisplayHandle>,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> crate::event_loop::EventLoopProxy {
crate::event_loop::EventLoopProxy {
event_loop_proxy: crate::platform_impl::EventLoopProxy::Wayland(
self.event_loop_proxy.clone(),
),
}
fn create_proxy(&self) -> CoreEventLoopProxy {
self.event_loop_proxy.clone()
}
fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -622,7 +596,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_custom_cursor(
&self,
cursor: CustomCursorSource,
) -> Result<RootCustomCursor, ExternalError> {
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor {
inner: PlatformCustomCursor::Wayland(OnlyCursorImage(Arc::from(cursor.inner.0))),
})
@@ -636,10 +610,9 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<crate::window::Window, RootOsError> {
) -> Result<Box<dyn crate::window::Window>, RequestError> {
let window = crate::platform_impl::wayland::Window::new(self, window_attributes)?;
let window = crate::platform_impl::Window::Wayland(window);
Ok(crate::window::Window { window })
Ok(Box::new(window))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
@@ -659,18 +632,10 @@ impl RootActiveEventLoop for ActiveEventLoop {
None
}
fn owned_display_handle(&self) -> crate::event_loop::OwnedDisplayHandle {
crate::event_loop::OwnedDisplayHandle {
platform: crate::platform_impl::OwnedDisplayHandle::Wayland(self.connection.clone()),
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(self.handle.clone())
}
#[inline(always)]
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
@@ -690,8 +655,23 @@ impl ActiveEventLoop {
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
self.handle.display_handle()
}
}
pub struct OwnedDisplayHandle {
pub(crate) connection: Connection,
}
impl OwnedDisplayHandle {
fn new(connection: Connection) -> Self {
Self { connection }
}
}
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
use sctk::reexports::client::Proxy;

View File

@@ -1,19 +1,30 @@
//! An event loop proxy.
use std::sync::Arc;
use sctk::reexports::calloop::ping::Ping;
use crate::event_loop::{EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider};
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.ping.ping();
}
}
impl EventLoopProxy {
pub fn new(ping: Ping) -> Self {
Self { ping }
}
}
pub fn wake_up(&self) {
self.ping.ping();
impl From<EventLoopProxy> for CoreEventLoopProxy {
fn from(value: EventLoopProxy) -> Self {
CoreEventLoopProxy::new(Arc::new(value))
}
}

View File

@@ -2,10 +2,8 @@
use std::vec::Drain;
use super::{DeviceId, WindowId};
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
use crate::event::{DeviceEvent, Event, WindowEvent};
use crate::window::WindowId;
/// An event loop's sink to deliver events from the Wayland event callbacks
/// to the winit's user.
@@ -27,17 +25,14 @@ impl EventSink {
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
});
pub fn push_device_event(&mut self, event: DeviceEvent) {
self.window_events.push(Event::DeviceEvent { event, device_id: None });
}
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) {
self.window_events.push(Event::WindowEvent { event, window_id: RootWindowId(window_id) });
self.window_events.push(Event::WindowEvent { event, window_id });
}
#[inline]

View File

@@ -1,18 +1,14 @@
//! Winit's Wayland backend.
use std::fmt::Display;
use std::sync::Arc;
pub use event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use output::{MonitorHandle, VideoModeHandle};
use sctk::reexports::client::globals::{BindError, GlobalError};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy};
use sctk::reexports::client::Proxy;
pub use window::Window;
pub(super) use crate::cursor::OnlyCursorImage as CustomCursor;
use crate::dpi::{LogicalSize, PhysicalSize};
pub use crate::platform_impl::platform::{OsError, WindowId};
use crate::window::WindowId;
mod event_loop;
mod output;
@@ -21,69 +17,10 @@ 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;
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId(i32);
impl FingerId {
pub const fn dummy() -> Self {
FingerId(0)
}
}
/// Get the WindowId out of the surface.
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.id().as_ptr() as u64)
WindowId::from_raw(surface.id().as_ptr() as usize)
}
/// The default routine does floor, but we need round on Wayland.

View File

@@ -17,7 +17,7 @@ use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
use crate::platform_impl::wayland::{self, WindowId};
impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
fn event(
@@ -369,10 +369,9 @@ fn key_input(
None => return,
};
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);
let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false };
let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false };
event_sink.push_window_event(event, window_id);
}
}

View File

@@ -40,6 +40,9 @@ pub struct WinitSeatState {
/// The mapping from touched points to the surfaces they're present.
touch_map: AHashMap<i32, TouchPoint>,
/// Id of the first touch event.
first_touch_id: Option<i32>,
/// The text input bound on the seat.
text_input: Option<Arc<ZwpTextInputV3>>,

View File

@@ -27,10 +27,10 @@ use sctk::seat::pointer::{
use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, PointerSource, PointerKind, TouchPhase, WindowEvent};
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
use crate::platform_impl::wayland::{self, WindowId};
pub mod relative_pointer;
@@ -59,8 +59,6 @@ impl PointerHandler for WinitState {
},
};
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
for event in events {
let surface = &event.surface;
@@ -124,18 +122,20 @@ impl PointerHandler for WinitState {
},
// Regular events on the main surface.
PointerEventKind::Enter { .. } => {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
self.events_sink.push_window_event(
WindowEvent::PointerEntered {
primary: true,
device_id: None,
position,
kind: PointerKind::Mouse,
},
window_id,
);
window.pointer_entered(Arc::downgrade(themed_pointer));
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
self.events_sink.push_window_event(
WindowEvent::CursorMoved { device_id, position },
window_id,
);
},
PointerEventKind::Leave { .. } => {
window.pointer_left(Arc::downgrade(themed_pointer));
@@ -143,12 +143,24 @@ impl PointerHandler for WinitState {
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
self.events_sink
.push_window_event(WindowEvent::CursorLeft { device_id }, window_id);
self.events_sink.push_window_event(
WindowEvent::PointerLeft {
primary: true,
device_id: None,
position: Some(position),
kind: PointerKind::Mouse,
},
window_id,
);
},
PointerEventKind::Motion { .. } => {
self.events_sink.push_window_event(
WindowEvent::CursorMoved { device_id, position },
WindowEvent::PointerMoved {
primary: true,
device_id: None,
position,
source: PointerSource::Mouse,
},
window_id,
);
},
@@ -164,7 +176,13 @@ impl PointerHandler for WinitState {
ElementState::Released
};
self.events_sink.push_window_event(
WindowEvent::MouseInput { device_id, state, button },
WindowEvent::PointerButton {
primary: true,
device_id: None,
state,
position,
button: button.into(),
},
window_id,
);
},
@@ -209,7 +227,7 @@ impl PointerHandler for WinitState {
};
self.events_sink.push_window_event(
WindowEvent::MouseWheel { device_id, delta, phase },
WindowEvent::MouseWheel { device_id: None, delta, phase },
window_id,
)
},

View File

@@ -66,10 +66,9 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
},
_ => return,
};
state.events_sink.push_device_event(
DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel) },
super::DeviceId,
);
state
.events_sink
.push_device_event(DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) });
}
}

View File

@@ -8,9 +8,9 @@ use sctk::seat::touch::{TouchData, TouchHandler};
use tracing::warn;
use crate::dpi::LogicalPosition;
use crate::event::{Touch, TouchPhase, WindowEvent};
use crate::event::{ButtonSource, ElementState, FingerId, PointerKind, PointerSource, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, FingerId};
impl TouchHandler for WinitState {
fn down(
@@ -40,20 +40,33 @@ impl TouchHandler for WinitState {
// Update the state of the point.
let location = LogicalPosition::<f64>::from(position);
// Only update primary finger once we don't have any touch.
if seat_state.touch_map.is_empty() {
seat_state.first_touch_id = Some(id);
}
let primary = seat_state.first_touch_id == Some(id);
seat_state.touch_map.insert(id, TouchPoint { surface, location });
let position = location.to_physical(scale_factor);
let finger_id = FingerId::from_raw(id as usize);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Started,
location: location.to_physical(scale_factor),
force: None,
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
FingerId(id),
)),
}),
WindowEvent::PointerEntered {
device_id: None,
primary,
position,
kind: PointerKind::Touch(finger_id),
},
window_id,
);
self.events_sink.push_window_event(
WindowEvent::PointerButton {
device_id: None,
primary,
state: ElementState::Pressed,
position,
button: ButtonSource::Touch { finger_id, force: None },
},
window_id,
);
}
@@ -81,24 +94,41 @@ impl TouchHandler for WinitState {
None => return,
};
// Update the primary touch point.
let primary = seat_state.first_touch_id == Some(id);
// Reset primary finger once all the other fingers are lifted to not transfer primary
// finger to some other finger and still accept it when it's briefly moved between the
// windows.
if seat_state.touch_map.is_empty() {
seat_state.first_touch_id = None;
}
let window_id = wayland::make_wid(&touch_point.surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
None => return,
};
let position = touch_point.location.to_physical(scale_factor);
let finger_id = FingerId::from_raw(id as usize);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Ended,
location: touch_point.location.to_physical(scale_factor),
force: None,
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
FingerId(id),
)),
}),
WindowEvent::PointerButton {
device_id: None,
primary,
state: ElementState::Released,
position,
button: ButtonSource::Touch { finger_id, force: None },
},
window_id,
);
self.events_sink.push_window_event(
WindowEvent::PointerLeft {
device_id: None,
primary,
position: Some(position),
kind: PointerKind::Touch(finger_id),
},
window_id,
);
}
@@ -126,6 +156,8 @@ impl TouchHandler for WinitState {
None => return,
};
let primary = seat_state.first_touch_id == Some(id);
let window_id = wayland::make_wid(&touch_point.surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
@@ -135,17 +167,15 @@ impl TouchHandler for WinitState {
touch_point.location = LogicalPosition::<f64>::from(position);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Moved,
location: touch_point.location.to_physical(scale_factor),
force: None,
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
FingerId(id),
)),
}),
WindowEvent::PointerMoved {
device_id: None,
primary,
position: touch_point.location.to_physical(scale_factor),
source: PointerSource::Touch {
finger_id: FingerId::from_raw(id as usize),
force: None,
},
},
window_id,
);
}
@@ -166,23 +196,21 @@ impl TouchHandler for WinitState {
None => return,
};
let location = touch_point.location.to_physical(scale_factor);
let primary = seat_state.first_touch_id == Some(id);
let position = touch_point.location.to_physical(scale_factor);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
location,
force: None,
finger_id: crate::event::FingerId(crate::platform_impl::FingerId::Wayland(
FingerId(id),
)),
}),
WindowEvent::PointerLeft {
device_id: None,
primary,
position: Some(position),
kind: PointerKind::Touch(FingerId::from_raw(id as usize)),
},
window_id,
);
}
seat_state.first_touch_id = None;
}
fn shape(

View File

@@ -21,6 +21,7 @@ use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::error::OsError;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{
@@ -32,8 +33,7 @@ use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScali
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::{WaylandError, WindowId};
use crate::platform_impl::OsError;
use crate::platform_impl::wayland::WindowId;
/// Winit's Wayland state.
pub struct WinitState {
@@ -126,7 +126,7 @@ impl WinitState {
) -> Result<Self, OsError> {
let registry_state = RegistryState::new(globals);
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
CompositorState::bind(globals, queue_handle).map_err(|err| os_error!(err))?;
let subcompositor_state = match SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
@@ -156,7 +156,7 @@ impl WinitState {
(None, None)
};
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let shm = Shm::bind(globals, queue_handle).map_err(|err| os_error!(err))?;
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
Ok(Self {
@@ -168,7 +168,7 @@ impl WinitState {
shm,
custom_cursor_pool,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(),

View File

@@ -14,8 +14,7 @@ use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::WindowId;
use crate::window::ActivationToken;
use crate::window::{ActivationToken, WindowId};
pub struct XdgActivationState {
xdg_activation: XdgActivationV1,

View File

@@ -16,17 +16,17 @@ use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData;
use super::{ActiveEventLoop, WaylandError, WindowId};
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use super::ActiveEventLoop;
use crate::dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform_impl::{Fullscreen, MonitorHandle as PlatformMonitorHandle};
use crate::window::{
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme,
UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
};
pub(crate) mod state;
@@ -77,7 +77,7 @@ impl Window {
pub(crate) fn new(
event_loop_window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RootOsError> {
) -> Result<Self, RequestError> {
let queue_handle = event_loop_window_target.queue_handle.clone();
let mut state = event_loop_window_target.state.borrow_mut();
@@ -87,9 +87,9 @@ impl Window {
let compositor = state.compositor_state.clone();
let xdg_activation =
state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone());
let display = event_loop_window_target.connection.display();
let display = event_loop_window_target.handle.connection.display();
let size: Size = attributes.inner_size.unwrap_or(LogicalSize::new(800., 600.).into());
let size: Size = attributes.surface_size.unwrap_or(LogicalSize::new(800., 600.).into());
// We prefer server side decorations, however to not have decorations we ask for client
// side decorations instead.
@@ -103,7 +103,7 @@ impl Window {
state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
let mut window_state = WindowState::new(
event_loop_window_target.connection.clone(),
event_loop_window_target.handle.clone(),
&event_loop_window_target.queue_handle,
&state,
size,
@@ -129,10 +129,10 @@ impl Window {
// Set the min and max sizes. We must set the hints upon creating a window, so
// we use the default `1.` scaling...
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
window_state.set_min_inner_size(min_size);
window_state.set_max_inner_size(max_size);
let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.));
let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.));
window_state.set_min_surface_size(min_size);
window_state.set_max_surface_size(max_size);
// Non-resizable implies that the min and max sizes are set to the same value.
window_state.set_resizable(attributes.resizable);
@@ -190,15 +190,11 @@ impl Window {
let event_queue = wayland_source.queue();
// Do a roundtrip.
event_queue.roundtrip(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?;
// XXX Wait for the initial configure to arrive.
while !window_state.lock().unwrap().is_configured() {
event_queue.blocking_dispatch(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?;
}
// Wake-up event loop, so it'll send initial redraw requested.
@@ -223,51 +219,63 @@ impl Window {
}
impl Window {
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()),
};
let serial = AsyncRequestSerial::get();
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
xdg_activation_token.set_surface(self.surface());
xdg_activation_token.commit();
Ok(serial)
}
#[inline]
pub fn id(&self) -> WindowId {
pub fn surface(&self) -> &WlSurface {
self.window.wl_surface()
}
}
impl Drop for Window {
fn drop(&mut self) {
self.window_requests.closed.store(true, Ordering::Relaxed);
self.event_loop_awakener.ping();
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::WaylandWindowHandle::new({
let ptr = self.window.wl_surface().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
});
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) }
}
}
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::WaylandDisplayHandle::new({
let ptr = self.display.id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
});
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) }
}
}
impl CoreWindow for Window {
fn id(&self) -> WindowId {
self.window_id
}
#[inline]
pub fn set_title(&self, title: impl ToString) {
let new_title = title.to_string();
self.window_state.lock().unwrap().set_title(new_title);
}
#[inline]
pub fn set_visible(&self, _visible: bool) {
// Not possible on Wayland.
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _: Position) {
// Not possible on Wayland.
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
}
#[inline]
pub fn request_redraw(&self) {
fn request_redraw(&self) {
// NOTE: try to not wake up the loop when the event was already scheduled and not yet
// processed by the loop, because if at this point the value was `true` it could only
// mean that the loop still haven't dispatched the value to the client and will do
@@ -283,135 +291,119 @@ impl Window {
}
#[inline]
pub fn pre_present_notify(&self) {
fn title(&self) -> String {
self.window_state.lock().unwrap().title().to_owned()
}
fn pre_present_notify(&self) {
self.window_state.lock().unwrap().request_frame_callback();
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
fn reset_dead_keys(&self) {
crate::platform_impl::common::xkb::reset_dead_keys()
}
fn surface_position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
}
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
Err(NotSupportedError::new("window position information is not available on Wayland")
.into())
}
fn set_outer_position(&self, _position: Position) {
// Not possible.
}
fn surface_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.surface_size(), scale_factor)
}
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let mut window_state = self.window_state.lock().unwrap();
let new_size = window_state.request_surface_size(size);
self.request_redraw();
Some(new_size)
}
fn outer_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
}
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let mut window_state = self.window_state.lock().unwrap();
let new_size = window_state.request_inner_size(size);
self.request_redraw();
Some(new_size)
fn safe_area(&self) -> PhysicalInsets<u32> {
PhysicalInsets::new(0, 0, 0, 0)
}
/// Set the minimum inner size for the window.
#[inline]
pub fn set_min_inner_size(&self, min_size: Option<Size>) {
fn set_min_surface_size(&self, min_size: Option<Size>) {
let scale_factor = self.scale_factor();
let min_size = min_size.map(|size| size.to_logical(scale_factor));
self.window_state.lock().unwrap().set_min_inner_size(min_size);
self.window_state.lock().unwrap().set_min_surface_size(min_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
/// Set the maximum inner size for the window.
/// Set the maximum surface size for the window.
#[inline]
pub fn set_max_inner_size(&self, max_size: Option<Size>) {
fn set_max_surface_size(&self, max_size: Option<Size>) {
let scale_factor = self.scale_factor();
let max_size = max_size.map(|size| size.to_logical(scale_factor));
self.window_state.lock().unwrap().set_max_inner_size(max_size);
self.window_state.lock().unwrap().set_max_surface_size(max_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`set_resize_increments` is not implemented for Wayland");
fn set_surface_resize_increments(&self, _increments: Option<Size>) {
warn!("`set_surface_resize_increments` is not implemented for Wayland");
}
fn set_title(&self, title: &str) {
let new_title = title.to_string();
self.window_state.lock().unwrap().set_title(new_title);
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
fn set_transparent(&self, transparent: bool) {
self.window_state.lock().unwrap().set_transparent(transparent);
}
#[inline]
pub fn has_focus(&self) -> bool {
self.window_state.lock().unwrap().has_focus()
fn set_visible(&self, _visible: bool) {
// Not possible on Wayland.
}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
// XXX clients don't know whether they are minimized or not.
fn is_visible(&self) -> Option<bool> {
None
}
#[inline]
pub fn show_window_menu(&self, position: Position) {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
self.window_state.lock().unwrap().show_window_menu(position);
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().drag_resize_window(direction)
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
fn set_resizable(&self, resizable: bool) {
if self.window_state.lock().unwrap().set_resizable(resizable) {
// NOTE: Requires commit to be applied.
self.request_redraw();
}
}
#[inline]
pub fn is_resizable(&self) -> bool {
fn is_resizable(&self) -> bool {
self.window_state.lock().unwrap().resizable()
}
#[inline]
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
fn set_enabled_buttons(&self, _buttons: WindowButtons) {
// TODO(kchibisov) v5 of the xdg_shell allows that.
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
fn enabled_buttons(&self) -> WindowButtons {
// TODO(kchibisov) v5 of the xdg_shell allows that.
WindowButtons::all()
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.window_state.lock().unwrap().scale_factor()
}
#[inline]
pub fn set_blur(&self, blur: bool) {
self.window_state.lock().unwrap().set_blur(blur);
}
#[inline]
pub fn set_decorations(&self, decorate: bool) {
self.window_state.lock().unwrap().set_decorate(decorate)
}
#[inline]
pub fn is_decorated(&self) -> bool {
self.window_state.lock().unwrap().is_decorated()
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {}
#[inline]
pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
fn set_minimized(&self, minimized: bool) {
// You can't unminimize the window on Wayland.
if !minimized {
warn!("Unminimizing is ignored on Wayland.");
@@ -421,8 +413,20 @@ impl Window {
self.window.set_minimized();
}
#[inline]
pub fn is_maximized(&self) -> bool {
fn is_minimized(&self) -> Option<bool> {
// XXX clients don't know whether they are minimized or not.
None
}
fn set_maximized(&self, maximized: bool) {
if maximized {
self.window.set_maximized()
} else {
self.window.unset_maximized()
}
}
fn is_maximized(&self) -> bool {
self.window_state
.lock()
.unwrap()
@@ -432,43 +436,14 @@ impl Window {
.unwrap_or_default()
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.window.set_maximized()
} else {
self.window.unset_maximized()
}
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
let is_fullscreen = self
.window_state
.lock()
.unwrap()
.last_configure
.as_ref()
.map(|last_configure| last_configure.is_fullscreen())
.unwrap_or_default();
if is_fullscreen {
let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland);
Some(Fullscreen::Borderless(current_monitor))
} else {
None
}
}
#[inline]
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
fn set_fullscreen(&self, fullscreen: Option<CoreFullscreen>) {
match fullscreen {
Some(Fullscreen::Exclusive(_)) => {
Some(CoreFullscreen::Exclusive(_)) => {
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
},
#[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
Some(Fullscreen::Borderless(monitor)) => {
let output = monitor.and_then(|monitor| match monitor {
Some(CoreFullscreen::Borderless(monitor)) => {
let output = monitor.and_then(|monitor| match monitor.inner {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
#[cfg(x11_platform)]
PlatformMonitorHandle::X(_) => None,
@@ -480,22 +455,82 @@ impl Window {
}
}
#[inline]
pub fn set_cursor(&self, cursor: Cursor) {
let window_state = &mut self.window_state.lock().unwrap();
fn fullscreen(&self) -> Option<CoreFullscreen> {
let is_fullscreen = self
.window_state
.lock()
.unwrap()
.last_configure
.as_ref()
.map(|last_configure| last_configure.is_fullscreen())
.unwrap_or_default();
match cursor {
Cursor::Icon(icon) => window_state.set_cursor(icon),
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
if is_fullscreen {
let current_monitor = self.current_monitor();
Some(CoreFullscreen::Borderless(current_monitor))
} else {
None
}
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window_state.lock().unwrap().set_cursor_visible(visible);
fn scale_factor(&self) -> f64 {
self.window_state.lock().unwrap().scale_factor()
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
#[inline]
fn set_blur(&self, blur: bool) {
self.window_state.lock().unwrap().set_blur(blur);
}
#[inline]
fn set_decorations(&self, decorate: bool) {
self.window_state.lock().unwrap().set_decorate(decorate)
}
#[inline]
fn is_decorated(&self) -> bool {
self.window_state.lock().unwrap().is_decorated()
}
fn set_window_level(&self, _level: WindowLevel) {}
fn set_window_icon(&self, _window_icon: Option<crate::window::Icon>) {}
#[inline]
fn set_ime_cursor_area(&self, position: Position, size: Size) {
let window_state = self.window_state.lock().unwrap();
if window_state.ime_allowed() {
let scale_factor = window_state.scale_factor();
let position = position.to_logical(scale_factor);
let size = size.to_logical(scale_factor);
window_state.set_ime_cursor_area(position, size);
}
}
#[inline]
fn set_ime_allowed(&self, allowed: bool) {
let mut window_state = self.window_state.lock().unwrap();
if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
self.event_loop_awakener.ping();
}
}
#[inline]
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.window_state.lock().unwrap().set_ime_purpose(purpose);
}
fn focus_window(&self) {}
fn has_focus(&self) -> bool {
self.window_state.lock().unwrap().has_focus()
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => {
@@ -521,29 +556,26 @@ impl Window {
xdg_activation_token.commit();
}
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => return Err(NotSupportedError::new()),
};
let serial = AsyncRequestSerial::get();
let data = XdgActivationTokenData::Obtain((self.window_id, serial));
let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
xdg_activation_token.set_surface(self.surface());
xdg_activation_token.commit();
Ok(serial)
fn set_theme(&self, theme: Option<Theme>) {
self.window_state.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().set_cursor_grab(mode)
fn theme(&self) -> Option<Theme> {
self.window_state.lock().unwrap().theme()
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
fn set_content_protected(&self, _protected: bool) {}
fn set_cursor(&self, cursor: Cursor) {
let window_state = &mut self.window_state.lock().unwrap();
match cursor {
Cursor::Icon(icon) => window_state.set_cursor(icon),
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
}
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
self.window_state
@@ -554,124 +586,76 @@ impl Window {
.map(|_| self.request_redraw())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
self.window_state.lock().unwrap().set_cursor_grab(mode)
}
fn set_cursor_visible(&self, visible: bool) {
self.window_state.lock().unwrap().set_cursor_visible(visible);
}
fn drag_window(&self) -> Result<(), RequestError> {
self.window_state.lock().unwrap().drag_window()
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
self.window_state.lock().unwrap().drag_resize_window(direction)
}
fn show_window_menu(&self, position: Position) {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
self.window_state.lock().unwrap().show_window_menu(position);
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
let surface = self.window.wl_surface();
if hittest {
surface.set_input_region(None);
Ok(())
} else {
let region = Region::new(&*self.compositor).map_err(|_| {
ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
})?;
let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?;
region.add(0, 0, 0, 0);
surface.set_input_region(Some(region.wl_region()));
Ok(())
}
}
#[inline]
pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
let window_state = self.window_state.lock().unwrap();
if window_state.ime_allowed() {
let scale_factor = window_state.scale_factor();
let position = position.to_logical(scale_factor);
let size = size.to_logical(scale_factor);
window_state.set_ime_cursor_area(position, size);
}
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
let mut window_state = self.window_state.lock().unwrap();
if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
self.event_loop_awakener.ping();
}
}
#[inline]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
self.window_state.lock().unwrap().set_ime_purpose(purpose);
}
#[inline]
pub fn focus_window(&self) {}
#[inline]
pub fn surface(&self) -> &WlSurface {
self.window.wl_surface()
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
let data = self.window.wl_surface().data::<SurfaceData>()?;
data.outputs().next().map(MonitorHandle::new)
data.outputs()
.next()
.map(MonitorHandle::new)
.map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| CoreMonitorHandle { inner })
}
#[inline]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
self.monitors.lock().unwrap().clone()
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
self.monitors
.lock()
.unwrap()
.clone()
.into_iter()
.map(crate::platform_impl::MonitorHandle::Wayland)
.map(|inner| CoreMonitorHandle { inner }),
)
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// XXX there's no such concept on Wayland.
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
// NOTE: There's no such concept on Wayland.
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
Ok(rwh_06::WaylandWindowHandle::new({
let ptr = self.window.wl_surface().id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
})
.into())
/// Get the raw-window-handle v0.6 display handle.
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::WaylandDisplayHandle::new({
let ptr = self.display.id().as_ptr();
std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
})
.into())
}
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
self.window_state.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.window_state.lock().unwrap().theme()
}
pub fn set_content_protected(&self, _protected: bool) {}
#[inline]
pub fn title(&self) -> String {
self.window_state.lock().unwrap().title().to_owned()
}
}
impl Drop for Window {
fn drop(&mut self) {
self.window_requests.closed.store(true, Ordering::Relaxed);
self.event_loop_awakener.ping();
/// Get the raw-window-handle v0.6 window handle.
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}

View File

@@ -10,7 +10,7 @@ use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::client::{Proxy, QueueHandle};
use sctk::reexports::csd_frame::{
DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
};
@@ -30,7 +30,8 @@ use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::error::{NotSupportedError, RequestError};
use crate::platform_impl::wayland::event_loop::OwnedDisplayHandle;
use crate::platform_impl::wayland::logical_to_physical_rounded;
use crate::platform_impl::wayland::seat::{
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
@@ -38,21 +39,21 @@ use crate::platform_impl::wayland::seat::{
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::{PlatformCustomCursor, WindowId};
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, WindowId};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
// Minimum window inner size.
// Minimum window surface size.
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
/// The state of the window which is being updated from the [`WinitState`].
pub struct WindowState {
/// The connection to Wayland server.
pub connection: Connection,
pub handle: Arc<OwnedDisplayHandle>,
/// The `Shm` to set cursor.
pub shm: WlShm,
@@ -112,7 +113,7 @@ pub struct WindowState {
/// The text inputs observed on the window.
text_inputs: Vec<ZwpTextInputV3>,
/// The inner size of the window, as in without client side decorations.
/// The surface size of the window, as in without client side decorations.
size: LogicalSize<u32>,
/// Whether the CSD fail to create, so we don't try to create them on each iteration.
@@ -122,8 +123,8 @@ pub struct WindowState {
decorate: bool,
/// Min size.
min_inner_size: LogicalSize<u32>,
max_inner_size: Option<LogicalSize<u32>>,
min_surface_size: LogicalSize<u32>,
max_surface_size: Option<LogicalSize<u32>>,
/// The size of the window when no states were applied to it. The primary use for it
/// is to fallback to original window size, before it was maximized, if the compositor
@@ -161,7 +162,7 @@ pub struct WindowState {
impl WindowState {
/// Create new window state.
pub fn new(
connection: Connection,
handle: Arc<OwnedDisplayHandle>,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
initial_size: Size,
@@ -183,7 +184,7 @@ impl WindowState {
blur: None,
blur_manager: winit_state.kwin_blur_manager.clone(),
compositor,
connection,
handle,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
selected_cursor: Default::default(),
@@ -197,8 +198,8 @@ impl WindowState {
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
last_configure: None,
max_inner_size: None,
min_inner_size: MIN_WINDOW_SIZE,
max_surface_size: None,
min_surface_size: MIN_WINDOW_SIZE,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
@@ -328,7 +329,7 @@ impl WindowState {
// Apply configure bounds only when compositor let the user decide what size to pick.
if constrain {
let bounds = self.inner_size_bounds(&configure);
let bounds = self.surface_size_bounds(&configure);
new_size.width =
bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
new_size.height = bounds
@@ -353,7 +354,7 @@ impl WindowState {
// NOTE: Set the configure before doing a resize, since we query it during it.
self.last_configure = Some(configure);
if state_change_requires_resize || new_size != self.inner_size() {
if state_change_requires_resize || new_size != self.surface_size() {
self.resize(new_size);
true
} else {
@@ -361,8 +362,8 @@ impl WindowState {
}
}
/// Compute the bounds for the inner size of the surface.
fn inner_size_bounds(
/// Compute the bounds for the surface size of the surface.
fn surface_size_bounds(
&self,
configure: &WindowConfigure,
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
@@ -388,7 +389,7 @@ impl WindowState {
}
/// Start interacting drag resize.
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
let xdg_toplevel = self.window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
@@ -402,7 +403,7 @@ impl WindowState {
}
/// Start the window drag.
pub fn drag_window(&self) -> Result<(), ExternalError> {
pub fn drag_window(&self) -> Result<(), RequestError> {
let xdg_toplevel = self.window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
@@ -507,8 +508,8 @@ impl WindowState {
// Restore min/max sizes of the window.
self.reload_min_max_hints();
} else {
self.set_min_inner_size(Some(self.size));
self.set_max_inner_size(Some(self.size));
self.set_min_surface_size(Some(self.size));
self.set_max_surface_size(Some(self.size));
}
// Reload the state on the frame as well.
@@ -533,7 +534,7 @@ impl WindowState {
/// Get the size of the window.
#[inline]
pub fn inner_size(&self) -> LogicalSize<u32> {
pub fn surface_size(&self) -> LogicalSize<u32> {
self.size
}
@@ -628,21 +629,21 @@ impl WindowState {
}
/// Try to resize the window when the user can do so.
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
self.resize(inner_size.to_logical(self.scale_factor()))
self.resize(surface_size.to_logical(self.scale_factor()))
}
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
logical_to_physical_rounded(self.surface_size(), self.scale_factor())
}
/// Resize the window to the new inner size.
fn resize(&mut self, inner_size: LogicalSize<u32>) {
self.size = inner_size;
/// Resize the window to the new surface size.
fn resize(&mut self, surface_size: LogicalSize<u32>) {
self.size = surface_size;
// Update the stateless size.
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
self.stateless_size = inner_size;
self.stateless_size = surface_size;
}
// Update the inner frame.
@@ -673,7 +674,7 @@ impl WindowState {
// Update the target viewport, this is used if and only if fractional scaling is in use.
if let Some(viewport) = self.viewport.as_ref() {
// Set inner size without the borders.
// Set surface size without the borders.
viewport.set_destination(self.size.width as _, self.size.height as _);
}
}
@@ -693,7 +694,7 @@ impl WindowState {
}
self.apply_on_pointer(|pointer, _| {
if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
if pointer.set_cursor(&self.handle.connection, cursor_icon).is_err() {
warn!("Failed to set cursor to {:?}", cursor_icon);
}
})
@@ -753,7 +754,7 @@ impl WindowState {
}
/// Set maximum inner window size.
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
// Ensure that the window has the right minimum size.
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
size.width = size.width.max(MIN_WINDOW_SIZE.width);
@@ -766,12 +767,12 @@ impl WindowState {
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size);
self.min_inner_size = size;
self.min_surface_size = size;
self.window.set_min_size(Some(size.into()));
}
/// Set maximum inner window size.
pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
let size = size.map(|size| {
self.frame
.as_ref()
@@ -779,7 +780,7 @@ impl WindowState {
.unwrap_or(size)
});
self.max_inner_size = size;
self.max_surface_size = size;
self.window.set_max_size(size.map(Into::into));
}
@@ -799,7 +800,7 @@ impl WindowState {
}
/// Set the cursor grabbing state on the top-level.
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
if self.cursor_grab_mode.user_grab_mode == mode {
return Ok(());
}
@@ -812,16 +813,20 @@ impl WindowState {
/// Reload the hints for minimum and maximum sizes.
pub fn reload_min_max_hints(&mut self) {
self.set_min_inner_size(Some(self.min_inner_size));
self.set_max_inner_size(self.max_inner_size);
self.set_min_surface_size(Some(self.min_surface_size));
self.set_max_surface_size(self.max_surface_size);
}
/// Set the grabbing state on the surface.
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
let pointer_constraints = match self.pointer_constraints.as_ref() {
Some(pointer_constraints) => pointer_constraints,
None if mode == CursorGrabMode::None => return Ok(()),
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
None => {
return Err(
NotSupportedError::new("zwp_pointer_constraints is not available").into()
)
},
};
// Replace the current mode.
@@ -865,16 +870,17 @@ impl WindowState {
}
/// Set the position of the cursor.
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), RequestError> {
if self.pointer_constraints.is_none() {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
return Err(NotSupportedError::new("zwp_pointer_constraints is not available").into());
}
// Position can be set only for locked cursor.
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
"cursor position can be set only for locked cursor."
))));
return Err(NotSupportedError::new(
"cursor position could only be changed for locked pointer",
)
.into());
}
self.apply_on_pointer(|_, data| {

View File

@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
buffer: &'a mut Vec<u8>,
}
impl<'a> std::fmt::Write for Writer<'a> {
impl std::fmt::Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())
}
}
write!(Writer { buffer }, "{}", display).unwrap();
write!(Writer { buffer }, "{display}").unwrap();
}
#[cfg(test)]

View File

@@ -22,8 +22,9 @@ use xkbcommon_dl::xkb_mod_mask_t;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::event::{
DeviceEvent, ElementState, Event, Ime, InnerSizeWriter, MouseButton, MouseScrollDelta,
RawKeyEvent, Touch, TouchPhase, WindowEvent,
ButtonSource, DeviceEvent, DeviceId, ElementState, Event, FingerId, Ime, MouseButton,
MouseScrollDelta, PointerKind, PointerSource, RawKeyEvent, SurfaceSizeWriter, TouchPhase,
WindowEvent,
};
use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::{self, XkbState};
@@ -33,8 +34,8 @@ use crate::platform_impl::platform::x11::ActiveEventLoop;
use crate::platform_impl::x11::atoms::*;
use crate::platform_impl::x11::util::cookie::GenericEventCookie;
use crate::platform_impl::x11::{
mkdid, mkfid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState,
ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
mkdid, mkwid, util, CookieResultExt, Device, DeviceInfo, Dnd, DndState, ImeReceiver,
ScrollOrientation, UnownedWindow, WindowId,
};
/// The maximum amount of X modifiers to replay.
@@ -142,8 +143,18 @@ impl EventProcessor {
{
let event_type = xev.get_type();
if self.filter_event(xev) {
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
// If we have IME disabled, don't try to `filter_event`, since only IME can consume them
// and forward back. This is not desired for e.g. games since some IMEs may delay the input
// and game can toggle IME back when e.g. typing into some field where latency won't really
// matter.
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
let ime = self.target.ime.as_ref();
let window = self.active_window.map(|window| window as XWindow);
let forward_to_ime = ime
.and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window)))
.unwrap_or(false);
if forward_to_ime && self.filter_event(xev) {
let xev: &XKeyEvent = xev.as_ref();
if self.xmodmap.is_modifier(xev.keycode as u8) {
// Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
@@ -156,7 +167,8 @@ impl EventProcessor {
self.xfiltered_modifiers.push_front(xev.serial);
}
}
return;
} else {
self.filter_event(xev);
}
match event_type {
@@ -238,15 +250,8 @@ impl EventProcessor {
self.xinput2_unfocused(xev, &mut callback);
},
xinput2::XI_TouchBegin | xinput2::XI_TouchUpdate | xinput2::XI_TouchEnd => {
let phase = match evtype {
xinput2::XI_TouchBegin => TouchPhase::Started,
xinput2::XI_TouchUpdate => TouchPhase::Moved,
xinput2::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!(),
};
let xev: &XIDeviceEvent = unsafe { xev.as_event() };
self.xinput2_touch(xev, phase, &mut callback);
self.xinput2_touch(xev, evtype, &mut callback);
},
xinput2::XI_RawButtonPress | xinput2::XI_RawButtonRelease => {
let state = match evtype {
@@ -326,7 +331,7 @@ impl EventProcessor {
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&self.target.xconn, device as _) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid as _), Device::new(info));
devices.insert(mkdid(info.deviceid as xinput::DeviceId), Device::new(info));
}
}
}
@@ -336,7 +341,7 @@ impl EventProcessor {
F: Fn(&Arc<UnownedWindow>) -> Ret,
{
let mut deleted = false;
let window_id = WindowId(window_id as _);
let window_id = WindowId::from_raw(window_id as _);
let result = self
.target
.windows
@@ -598,19 +603,19 @@ impl EventProcessor {
// `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root
// `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5
// We don't want to send `Moved` when this is false, since then every `Resized`
// We don't want to send `Moved` when this is false, since then every `SurfaceResized`
// (whether the window moved or not) is accompanied by an extraneous `Moved` event
// that has a position relative to the parent window.
let is_synthetic = xev.send_event == xlib::True;
// These are both in physical space.
let new_inner_size = (xev.width as u32, xev.height as u32);
let new_surface_size = (xev.width as u32, xev.height as u32);
let new_inner_position = (xev.x, xev.y);
let (mut resized, moved) = {
let mut shared_state_lock = window.shared_state_lock();
let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size);
let resized = util::maybe_change(&mut shared_state_lock.size, new_surface_size);
let moved = if is_synthetic {
util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position)
} else {
@@ -671,7 +676,7 @@ impl EventProcessor {
let last_scale_factor = shared_state_lock.last_monitor.scale_factor;
let new_scale_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
let window_rect = util::AaRect::new(new_outer_position, new_surface_size);
let monitor = self
.target
.xconn
@@ -695,27 +700,30 @@ impl EventProcessor {
&shared_state_lock,
);
let old_inner_size = PhysicalSize::new(width, height);
let new_inner_size = PhysicalSize::new(new_width, new_height);
let old_surface_size = PhysicalSize::new(width, height);
let new_surface_size = PhysicalSize::new(new_width, new_height);
// Unlock shared state to prevent deadlock in callback below
drop(shared_state_lock);
let inner_size = Arc::new(Mutex::new(new_inner_size));
let surface_size = Arc::new(Mutex::new(new_surface_size));
callback(&self.target, Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor: new_scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)),
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)),
},
});
let new_inner_size = *inner_size.lock().unwrap();
drop(inner_size);
let new_surface_size = *surface_size.lock().unwrap();
drop(surface_size);
if new_inner_size != old_inner_size {
window.request_inner_size_physical(new_inner_size.width, new_inner_size.height);
window.shared_state_lock().dpi_adjusted = Some(new_inner_size.into());
if new_surface_size != old_surface_size {
window.request_surface_size_physical(
new_surface_size.width,
new_surface_size.height,
);
window.shared_state_lock().dpi_adjusted = Some(new_surface_size.into());
// if the DPI factor changed, force a resize event to ensure the logical
// size is computed with the right DPI factor
resized = true;
@@ -736,13 +744,13 @@ impl EventProcessor {
// XResizeWindow requests, making Xorg, the winit client, and the WM
// consume 100% of CPU.
if let Some(adjusted_size) = shared_state_lock.dpi_adjusted {
if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
if new_surface_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
// When this finally happens, the event will not be synthetic.
shared_state_lock.dpi_adjusted = None;
} else {
// Unlock shared state to prevent deadlock in callback below
drop(shared_state_lock);
window.request_inner_size_physical(adjusted_size.0, adjusted_size.1);
window.request_surface_size_physical(adjusted_size.0, adjusted_size.1);
}
}
@@ -757,7 +765,7 @@ impl EventProcessor {
if resized {
callback(&self.target, Event::WindowEvent {
window_id,
event: WindowEvent::Resized(new_inner_size.into()),
event: WindowEvent::SurfaceResized(new_surface_size.into()),
});
}
}
@@ -802,7 +810,7 @@ impl EventProcessor {
// In the event that the window's been destroyed without being dropped first, we
// cleanup again here.
self.target.windows.borrow_mut().remove(&WindowId(window as _));
self.target.windows.borrow_mut().remove(&WindowId::from_raw(window as _));
// Since all XIM stuff needs to happen from the same thread, we destroy the input
// context here instead of when dropping the window.
@@ -875,7 +883,6 @@ impl EventProcessor {
};
let window_id = mkwid(window);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let keycode = xev.keycode as _;
@@ -939,7 +946,11 @@ impl EventProcessor {
let event = key_processor.process_key_event(keycode, state, repeat);
let event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: false },
event: WindowEvent::KeyboardInput {
device_id: None,
event,
is_synthetic: false,
},
};
callback(&self.target, event);
}
@@ -1009,7 +1020,7 @@ impl EventProcessor {
F: FnMut(&ActiveEventLoop, Event),
{
let window_id = mkwid(event.event as xproto::Window);
let device_id = mkdid(event.deviceid as xinput::DeviceId);
let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));
// Set the timestamp.
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
@@ -1019,16 +1030,30 @@ impl EventProcessor {
return;
}
let position = PhysicalPosition::new(event.event_x, event.event_y);
let event = match event.detail as u32 {
xlib::Button1 => {
WindowEvent::MouseInput { device_id, state, button: MouseButton::Left }
xlib::Button1 => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Left.into(),
},
xlib::Button2 => {
WindowEvent::MouseInput { device_id, state, button: MouseButton::Middle }
xlib::Button2 => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Middle.into(),
},
xlib::Button3 => {
WindowEvent::MouseInput { device_id, state, button: MouseButton::Right }
xlib::Button3 => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Right.into(),
},
// Suppress emulated scroll wheel clicks, since we handle the real motion events for
@@ -1046,10 +1071,28 @@ impl EventProcessor {
},
phase: TouchPhase::Moved,
},
8 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Back },
8 => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Back.into(),
},
9 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Forward },
x => WindowEvent::MouseInput { device_id, state, button: MouseButton::Other(x as u16) },
9 => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Forward.into(),
},
x => WindowEvent::PointerButton {
device_id,
primary: true,
state,
position,
button: MouseButton::Other(x as u16).into(),
},
};
let event = Event::WindowEvent { window_id, event };
@@ -1063,7 +1106,7 @@ impl EventProcessor {
// Set the timestamp.
self.target.xconn.set_timestamp(event.time as xproto::Timestamp);
let device_id = mkdid(event.deviceid as xinput::DeviceId);
let device_id = Some(mkdid(event.deviceid as xinput::DeviceId));
let window = event.event as xproto::Window;
let window_id = mkwid(window);
let new_cursor_pos = (event.event_x, event.event_y);
@@ -1078,7 +1121,12 @@ impl EventProcessor {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved { device_id, position },
event: WindowEvent::PointerMoved {
device_id,
primary: true,
position,
source: PointerSource::Mouse,
},
};
callback(&self.target, event);
} else if cursor_moved.is_none() {
@@ -1090,7 +1138,7 @@ impl EventProcessor {
slice::from_raw_parts(event.valuators.mask, event.valuators.mask_len as usize)
};
let mut devices = self.devices.borrow_mut();
let physical_device = match devices.get_mut(&DeviceId(event.sourceid as xinput::DeviceId)) {
let physical_device = match devices.get_mut(&mkdid(event.sourceid as xinput::DeviceId)) {
Some(device) => device,
None => return,
};
@@ -1149,7 +1197,7 @@ impl EventProcessor {
if device_info.deviceid == event.sourceid
|| device_info.attachment == event.sourceid
{
let device_id = DeviceId(device_info.deviceid as _);
let device_id = mkdid(device_info.deviceid as xinput::DeviceId);
if let Some(device) = devices.get_mut(&device_id) {
device.reset_scroll_position(device_info);
}
@@ -1158,15 +1206,17 @@ impl EventProcessor {
}
if self.window_exists(window) {
let device_id = Some(device_id);
let position = PhysicalPosition::new(event.event_x, event.event_y);
let event =
Event::WindowEvent { window_id, event: WindowEvent::CursorEntered { device_id } };
callback(&self.target, event);
let event = Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved { device_id, position },
event: WindowEvent::PointerEntered {
device_id,
primary: true,
position,
kind: PointerKind::Mouse,
},
};
callback(&self.target, event);
}
@@ -1186,8 +1236,11 @@ impl EventProcessor {
if self.window_exists(window) {
let event = Event::WindowEvent {
window_id: mkwid(window),
event: WindowEvent::CursorLeft {
device_id: mkdid(event.deviceid as xinput::DeviceId),
event: WindowEvent::PointerLeft {
device_id: Some(mkdid(event.deviceid as xinput::DeviceId)),
primary: true,
position: Some(PhysicalPosition::new(event.event_x, event.event_y)),
kind: PointerKind::Mouse,
},
};
callback(&self.target, event);
@@ -1238,16 +1291,20 @@ impl EventProcessor {
// The deviceid for this event is for a keyboard instead of a pointer,
// so we have to do a little extra work.
let pointer_id = self
let device_id = self
.devices
.borrow()
.get(&DeviceId(xev.deviceid as xinput::DeviceId))
.map(|device| device.attachment)
.unwrap_or(2);
.get(&mkdid(xev.deviceid as xinput::DeviceId))
.map(|device| mkdid(device.attachment as xinput::DeviceId));
let event = Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved { device_id: mkdid(pointer_id as _), position },
event: WindowEvent::PointerMoved {
device_id,
primary: true,
position,
source: PointerSource::Mouse,
},
};
callback(&self.target, event);
}
@@ -1303,7 +1360,7 @@ impl EventProcessor {
}
}
fn xinput2_touch<F>(&mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F)
fn xinput2_touch<F>(&mut self, xev: &XIDeviceEvent, phase: i32, mut callback: F)
where
F: FnMut(&ActiveEventLoop, Event),
{
@@ -1314,32 +1371,89 @@ impl EventProcessor {
if self.window_exists(window) {
let window_id = mkwid(window);
let id = xev.detail as u32;
let location = PhysicalPosition::new(xev.event_x, xev.event_y);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
// Mouse cursor position changes when touch events are received.
// Only the first concurrently active touch ID moves the mouse cursor.
if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) {
let is_first_touch =
is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase);
if is_first_touch {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
position: location.cast(),
event: WindowEvent::PointerMoved {
device_id: None,
primary: true,
position: position.cast(),
source: PointerSource::Mouse,
},
};
callback(&self.target, event);
}
let event = Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid as xinput::DeviceId),
phase,
location,
force: None, // TODO
finger_id: mkfid(id),
}),
};
callback(&self.target, event)
let device_id = Some(mkdid(xev.deviceid as xinput::DeviceId));
let finger_id = FingerId::from_raw(id as usize);
match phase {
xinput2::XI_TouchBegin => {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::PointerEntered {
device_id,
primary: is_first_touch,
position,
kind: PointerKind::Touch(finger_id),
},
};
callback(&self.target, event);
let event = Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id,
primary: is_first_touch,
state: ElementState::Pressed,
position,
button: ButtonSource::Touch { finger_id, force: None },
},
};
callback(&self.target, event);
},
xinput2::XI_TouchUpdate => {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::PointerMoved {
device_id,
primary: is_first_touch,
position,
source: PointerSource::Touch { finger_id, force: None },
},
};
callback(&self.target, event);
},
xinput2::XI_TouchEnd => {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id,
primary: is_first_touch,
state: ElementState::Released,
position,
button: ButtonSource::Touch { finger_id, force: None },
},
};
callback(&self.target, event);
let event = Event::WindowEvent {
window_id,
event: WindowEvent::PointerLeft {
device_id,
primary: is_first_touch,
position: Some(position),
kind: PointerKind::Touch(finger_id),
},
};
callback(&self.target, event);
},
_ => unreachable!(),
}
}
}
@@ -1352,7 +1466,7 @@ impl EventProcessor {
if xev.flags & xinput2::XIPointerEmulated == 0 {
let event = Event::DeviceEvent {
device_id: mkdid(xev.deviceid as xinput::DeviceId),
device_id: Some(mkdid(xev.deviceid as xinput::DeviceId)),
event: DeviceEvent::Button { state, button: xev.detail as u32 },
};
callback(&self.target, event);
@@ -1366,7 +1480,7 @@ impl EventProcessor {
// Set the timestamp.
self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
let did = mkdid(xev.deviceid as xinput::DeviceId);
let did = Some(mkdid(xev.deviceid as xinput::DeviceId));
let mask =
unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
@@ -1395,7 +1509,7 @@ impl EventProcessor {
if let Some(mouse_delta) = mouse_delta.consume() {
let event = Event::DeviceEvent {
device_id: did,
event: DeviceEvent::MouseMotion { delta: mouse_delta },
event: DeviceEvent::PointerMotion { delta: mouse_delta },
};
callback(&self.target, event);
}
@@ -1418,7 +1532,7 @@ impl EventProcessor {
// Set the timestamp.
self.target.xconn.set_timestamp(xev.time as xproto::Timestamp);
let device_id = mkdid(xev.sourceid as xinput::DeviceId);
let device_id = Some(mkdid(xev.sourceid as xinput::DeviceId));
let keycode = xev.detail as u32;
if keycode < KEYCODE_OFFSET as u32 {
return;
@@ -1440,7 +1554,7 @@ impl EventProcessor {
self.init_device(info.deviceid as xinput::DeviceId);
} else if 0 != info.flags & (xinput2::XISlaveRemoved | xinput2::XIMasterRemoved) {
let mut devices = self.devices.borrow_mut();
devices.remove(&DeviceId(info.deviceid as xinput::DeviceId));
devices.remove(&mkdid(info.deviceid as xinput::DeviceId));
}
}
}
@@ -1692,8 +1806,6 @@ impl EventProcessor {
) where
F: FnMut(&ActiveEventLoop, Event),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
// Update modifiers state and emit key events based on which keys are currently pressed.
let xcb = target.xconn.xcb_connection().get_raw_xcb_connection();
@@ -1716,7 +1828,7 @@ impl EventProcessor {
let event = key_processor.process_key_event(keycode as u32, state, false);
let event = Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: true },
event: WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: true },
};
callback(target, event);
}
@@ -1761,15 +1873,15 @@ impl EventProcessor {
}
}
fn is_first_touch(first: &mut Option<u32>, num: &mut u32, id: u32, phase: TouchPhase) -> bool {
fn is_first_touch(first: &mut Option<u32>, num: &mut u32, id: u32, phase: i32) -> bool {
match phase {
TouchPhase::Started => {
xinput2::XI_TouchBegin => {
if *num == 0 {
*first = Some(id);
}
*num += 1;
},
TouchPhase::Cancelled | TouchPhase::Ended => {
xinput2::XI_TouchEnd => {
if *first == Some(id) {
*first = None;
}

View File

@@ -1,5 +1,4 @@
pub use x11_dl::error::OpenError;
pub use x11_dl::xcursor::*;
pub use x11_dl::xinput2::*;
pub use x11_dl::xlib::*;
pub use x11_dl::xlib_xcb::*;

View File

@@ -1,7 +1,8 @@
use std::error::Error;
use std::ffi::CStr;
use std::os::raw::c_short;
use std::sync::Arc;
use std::{mem, ptr};
use std::{fmt, mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
@@ -19,6 +20,19 @@ pub enum ImeContextCreationError {
Null,
}
impl fmt::Display for ImeContextCreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImeContextCreationError::XError(err) => err.fmt(f),
ImeContextCreationError::Null => {
write!(f, "got null pointer from Xlib without exact reason")
},
}
}
}
impl Error for ImeContextCreationError {}
/// The callback used by XIM preedit functions.
type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer);

View File

@@ -225,6 +225,16 @@ impl Ime {
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
pub fn is_ime_allowed(&self, window: ffi::Window) -> bool {
if self.is_destroyed() {
false
} else if let Some(Some(context)) = self.inner.contexts.get(&window) {
context.is_allowed()
} else {
false
}
}
}
impl Drop for Ime {

View File

@@ -1,4 +1,3 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
@@ -19,24 +18,26 @@ use tracing::warn;
use x11rb::connection::RequestConnection;
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use x11rb::protocol::xkb;
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::protocol::{xkb, xproto};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError, OsError as RootOsError};
use crate::event::{Event, StartCause, WindowEvent};
use crate::error::{EventLoopError, RequestError};
use crate::event::{DeviceId, Event, StartCause, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
OwnedDisplayHandle as RootOwnedDisplayHandle,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::{min_timeout, WindowId};
use crate::platform_impl::{OsError, OwnedDisplayHandle, PlatformCustomCursor};
use crate::platform_impl::platform::min_timeout;
use crate::platform_impl::x11::window::Window;
use crate::platform_impl::PlatformCustomCursor;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowAttributes,
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow,
WindowAttributes, WindowId,
};
mod activation;
@@ -47,7 +48,7 @@ pub mod ffi;
mod ime;
mod monitor;
mod util;
mod window;
pub(crate) mod window;
mod xdisplay;
mod xsettings;
@@ -139,7 +140,7 @@ pub struct ActiveEventLoop {
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: WakeSender<WindowId>,
activation_sender: WakeSender<ActivationToken>,
event_loop_proxy: EventLoopProxy,
event_loop_proxy: CoreEventLoopProxy,
device_events: Cell<DeviceEvents>,
}
@@ -306,7 +307,7 @@ impl EventLoop {
sender: activation_token_sender, // not used again so no clone
waker: waker.clone(),
},
event_loop_proxy,
event_loop_proxy: event_loop_proxy.into(),
device_events: Default::default(),
};
@@ -399,9 +400,11 @@ impl EventLoop {
// `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
// X Server.
self.event_processor.target.x_connection().sync_with_server().map_err(|x_err| {
EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err)))))
})?;
self.event_processor
.target
.x_connection()
.sync_with_server()
.map_err(|x_err| EventLoopError::Os(os_error!(X11Error::Xlib(x_err))))?;
exit
}
@@ -519,13 +522,14 @@ impl EventLoop {
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
let token = self.event_processor.with_window(window_id.0 as xproto::Window, |window| {
window.generate_activation_token()
});
let token = self
.event_processor
.with_window(window_id.into_raw() as xproto::Window, |window| {
window.generate_activation_token()
});
match token {
Some(Ok(token)) => {
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
@@ -553,7 +557,6 @@ impl EventLoop {
}
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
app.window_event(
&self.event_processor.target,
window_id,
@@ -572,12 +575,9 @@ impl EventLoop {
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |window_target, event: Event| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
if let Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } = event
{
window_target.redraw_sender.send(wid);
window_target.redraw_sender.send(window_id);
} else {
match event {
Event::WindowEvent { window_id, event } => {
@@ -648,21 +648,6 @@ impl ActiveEventLoop {
.expect_then_ignore_error("Failed to update device event filter");
}
#[cfg(feature = "rwh_06")]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
let display_handle = rwh_06::XlibDisplayHandle::new(
// SAFETY: display will never be null
Some(
std::ptr::NonNull::new(self.xconn.display as *mut _)
.expect("X11 display should never be null"),
),
self.xconn.default_screen_index() as c_int,
);
Ok(display_handle.into())
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
@@ -677,27 +662,21 @@ impl ActiveEventLoop {
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> crate::event_loop::EventLoopProxy {
crate::event_loop::EventLoopProxy {
event_loop_proxy: crate::platform_impl::EventLoopProxy::X(
self.event_loop_proxy.clone(),
),
}
fn create_proxy(&self) -> CoreEventLoopProxy {
self.event_loop_proxy.clone()
}
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<crate::window::Window, RootOsError> {
let window = crate::platform_impl::x11::Window::new(self, window_attributes)?;
let window = crate::platform_impl::Window::X(window);
Ok(crate::window::Window { window })
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<RootCustomCursor, ExternalError> {
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor {
inner: PlatformCustomCursor::X(CustomCursor::new(self, custom_cursor.inner)?),
})
@@ -746,32 +725,18 @@ impl RootActiveEventLoop for ActiveEventLoop {
self.exit.get().is_some()
}
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
let handle = OwnedDisplayHandle::X(self.x_connection().clone());
RootOwnedDisplayHandle { platform: handle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(self.x_connection().clone())
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl EventLoopProxy {
pub fn wake_up(&self) {
self.ping.ping();
self.xconn.display_handle()
}
}
@@ -797,14 +762,14 @@ impl<'a> DeviceInfo<'a> {
}
}
impl<'a> Drop for DeviceInfo<'a> {
impl Drop for DeviceInfo<'_> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl<'a> Deref for DeviceInfo<'a> {
impl Deref for DeviceInfo<'_> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
@@ -812,70 +777,29 @@ impl<'a> Deref for DeviceInfo<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
pub const fn dummy() -> Self {
DeviceId(0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId(u32);
impl FingerId {
#[allow(unused)]
pub const fn dummy() -> Self {
FingerId(0)
}
}
pub(crate) struct Window(Arc<UnownedWindow>);
impl Deref for Window {
type Target = UnownedWindow;
#[inline]
fn deref(&self) -> &UnownedWindow {
&self.0
}
}
impl Window {
pub(crate) fn new(
event_loop: &ActiveEventLoop,
attribs: WindowAttributes,
) -> Result<Self, RootOsError> {
let window = Arc::new(UnownedWindow::new(event_loop, attribs)?);
event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window));
Ok(Window(window))
}
}
impl Drop for Window {
fn drop(&mut self) {
let window = self.deref();
let xconn = &window.xconn;
if let Ok(c) = xconn.xcb_connection().destroy_window(window.id().0 as xproto::Window) {
c.ignore_error();
}
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.ping.ping();
}
}
impl EventLoopProxy {
fn new(ping: Ping) -> Self {
Self { ping }
}
}
impl From<EventLoopProxy> for CoreEventLoopProxy {
fn from(value: EventLoopProxy) -> Self {
CoreEventLoopProxy::new(Arc::new(value))
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {
@@ -911,29 +835,35 @@ pub enum X11Error {
/// Failed to get property.
GetProperty(util::GetPropertyError),
/// Could not find an ARGB32 pict format.
NoArgb32Format,
}
impl fmt::Display for X11Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X11Error::Xlib(e) => write!(f, "Xlib error: {}", e),
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
X11Error::Xlib(e) => write!(f, "Xlib error: {e}"),
X11Error::Connect(e) => write!(f, "X11 connection error: {e}"),
X11Error::Connection(e) => write!(f, "X11 connection error: {e}"),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"),
X11Error::X11(e) => write!(f, "X11 error: {e:?}"),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"),
X11Error::InvalidActivationToken(s) => write!(
f,
"Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
X11Error::NoSuchVisual(visualid) => {
write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid)
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
},
X11Error::XsettingsParse(err) => {
write!(f, "Failed to parse xsettings: {:?}", err)
write!(f, "Failed to parse xsettings: {err:?}")
},
X11Error::NoArgb32Format => {
f.write_str("winit only supports X11 displays with ARGB32 picture formats")
},
}
}
@@ -1024,21 +954,17 @@ trait CookieResultExt {
fn expect_then_ignore_error(self, msg: &str);
}
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}
}
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
crate::window::WindowId::from_raw(w as _)
}
fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
}
fn mkfid(w: u32) -> crate::event::FingerId {
crate::event::FingerId(crate::platform_impl::FingerId::X(FingerId(w)))
fn mkdid(w: xinput::DeviceId) -> DeviceId {
DeviceId::from_raw(w as i64)
}
#[derive(Debug)]

View File

@@ -280,7 +280,7 @@ impl XConnection {
let info = self
.xcb_connection()
.extension_information(randr::X11_EXTENSION_NAME)?
.ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
.ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
// Select input data.
let event_mask =

View File

@@ -1,92 +1,161 @@
use std::ffi::CString;
use std::collections::hash_map::Entry;
use std::hash::{Hash, Hasher};
use std::iter;
use std::sync::Arc;
use std::{iter, slice};
use x11rb::connection::Connection;
use x11rb::protocol::render::{self, ConnectionExt as _};
use x11rb::protocol::xproto;
use super::super::ActiveEventLoop;
use super::*;
use crate::error::ExternalError;
use crate::platform_impl::{OsError, PlatformCustomCursorSource};
use crate::error::RequestError;
use crate::platform_impl::PlatformCustomCursorSource;
use crate::window::CursorIcon;
impl XConnection {
pub fn set_cursor_icon(&self, window: xproto::Window, cursor: Option<CursorIcon>) {
let cursor = *self
.cursor_cache
.lock()
.unwrap()
.entry(cursor)
.or_insert_with(|| self.get_cursor(cursor));
pub fn set_cursor_icon(
&self,
window: xproto::Window,
cursor: Option<CursorIcon>,
) -> Result<(), X11Error> {
let cursor = {
let mut cache = self.cursor_cache.lock().unwrap_or_else(|e| e.into_inner());
self.update_cursor(window, cursor).expect("Failed to set cursor");
}
pub(crate) fn set_custom_cursor(&self, window: xproto::Window, cursor: &CustomCursor) {
self.update_cursor(window, cursor.inner.cursor).expect("Failed to set cursor");
}
fn create_empty_cursor(&self) -> ffi::Cursor {
let data = 0;
let pixmap = unsafe {
let screen = (self.xlib.XDefaultScreen)(self.display);
let window = (self.xlib.XRootWindow)(self.display, screen);
(self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1)
match cache.entry(cursor) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => *v.insert(self.get_cursor(cursor)?),
}
};
if pixmap == 0 {
panic!("failed to allocate pixmap for cursor");
}
unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let mut dummy_color = MaybeUninit::uninit();
let cursor = (self.xlib.XCreatePixmapCursor)(
self.display,
pixmap,
pixmap,
dummy_color.as_mut_ptr(),
dummy_color.as_mut_ptr(),
0,
0,
);
(self.xlib.XFreePixmap)(self.display, pixmap);
cursor
}
self.update_cursor(window, cursor)
}
fn get_cursor(&self, cursor: Option<CursorIcon>) -> ffi::Cursor {
pub(crate) fn set_custom_cursor(
&self,
window: xproto::Window,
cursor: &CustomCursor,
) -> Result<(), X11Error> {
self.update_cursor(window, cursor.inner.cursor)
}
/// Create a cursor from an image.
fn create_cursor_from_image(
&self,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
image: &[u8],
) -> Result<xproto::Cursor, X11Error> {
// Create a pixmap for the default root window.
let root = self.default_root().root;
let pixmap =
xproto::PixmapWrapper::create_pixmap(self.xcb_connection(), 32, root, width, height)?;
// Create a GC to draw with.
let gc = xproto::GcontextWrapper::create_gc(
self.xcb_connection(),
pixmap.pixmap(),
&Default::default(),
)?;
// Draw the data into it.
self.xcb_connection()
.put_image(
xproto::ImageFormat::Z_PIXMAP,
pixmap.pixmap(),
gc.gcontext(),
width,
height,
0,
0,
0,
32,
image,
)?
.ignore_error();
drop(gc);
// Create the XRender picture.
let picture = render::PictureWrapper::create_picture(
self.xcb_connection(),
pixmap.pixmap(),
self.find_argb32_format()?,
&Default::default(),
)?;
drop(pixmap);
// Create the cursor.
let cursor = self.xcb_connection().generate_id()?;
self.xcb_connection()
.render_create_cursor(cursor, picture.picture(), hotspot_x, hotspot_y)?
.check()?;
Ok(cursor)
}
/// Find the render format that corresponds to ARGB32.
fn find_argb32_format(&self) -> Result<render::Pictformat, X11Error> {
macro_rules! direct {
($format:expr, $shift_name:ident, $mask_name:ident, $shift:expr) => {{
($format).direct.$shift_name == $shift && ($format).direct.$mask_name == 0xff
}};
}
self.render_formats()
.formats
.iter()
.find(|format| {
format.type_ == render::PictType::DIRECT
&& format.depth == 32
&& direct!(format, red_shift, red_mask, 16)
&& direct!(format, green_shift, green_mask, 8)
&& direct!(format, blue_shift, blue_mask, 0)
&& direct!(format, alpha_shift, alpha_mask, 24)
})
.ok_or(X11Error::NoArgb32Format)
.map(|format| format.id)
}
fn create_empty_cursor(&self) -> Result<xproto::Cursor, X11Error> {
self.create_cursor_from_image(1, 1, 0, 0, &[0, 0, 0, 0])
}
fn get_cursor(&self, cursor: Option<CursorIcon>) -> Result<xproto::Cursor, X11Error> {
let cursor = match cursor {
Some(cursor) => cursor,
None => return self.create_empty_cursor(),
};
let mut xcursor = 0;
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
let name = CString::new(name).unwrap();
xcursor = unsafe {
(self.xcursor.XcursorLibraryLoadCursor)(
self.display,
name.as_ptr() as *const c_char,
)
};
let database = self.database();
let handle = x11rb::cursor::Handle::new(
self.xcb_connection(),
self.default_screen_index(),
&database,
)?
.reply()?;
if xcursor != 0 {
break;
let mut last_error = None;
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
match handle.load_cursor(self.xcb_connection(), name) {
Ok(cursor) => return Ok(cursor),
Err(err) => last_error = Some(err.into()),
}
}
xcursor
Err(last_error.unwrap())
}
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {
fn update_cursor(
&self,
window: xproto::Window,
cursor: xproto::Cursor,
) -> Result<(), X11Error> {
self.xcb_connection()
.change_window_attributes(
window,
&xproto::ChangeWindowAttributesAux::new().cursor(cursor as xproto::Cursor),
&xproto::ChangeWindowAttributesAux::new().cursor(cursor),
)?
.ignore_error();
@@ -123,51 +192,44 @@ impl Eq for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(
event_loop: &ActiveEventLoop,
cursor: PlatformCustomCursorSource,
) -> Result<CustomCursor, ExternalError> {
unsafe {
let ximage = (event_loop.xconn.xcursor.XcursorImageCreate)(
cursor.0.width as i32,
cursor.0.height as i32,
);
if ximage.is_null() {
return Err(ExternalError::Os(os_error!(OsError::Misc(
"`XcursorImageCreate` failed"
))));
}
(*ximage).xhot = cursor.0.hotspot_x as u32;
(*ximage).yhot = cursor.0.hotspot_y as u32;
(*ximage).delay = 0;
mut cursor: PlatformCustomCursorSource,
) -> Result<CustomCursor, RequestError> {
// Reverse RGBA order to BGRA.
cursor.0.rgba.chunks_mut(4).for_each(|chunk| {
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
chunk[0..3].reverse();
let dst = slice::from_raw_parts_mut((*ximage).pixels, cursor.0.rgba.len() / 4);
for (dst, chunk) in dst.iter_mut().zip(cursor.0.rgba.chunks_exact(4)) {
*dst = (chunk[0] as u32) << 16
| (chunk[1] as u32) << 8
| (chunk[2] as u32)
| (chunk[3] as u32) << 24;
// Byteswap if we need to.
if event_loop.xconn.needs_endian_swap() {
let value = u32::from_ne_bytes(*chunk).swap_bytes();
*chunk = value.to_ne_bytes();
}
});
let cursor =
(event_loop.xconn.xcursor.XcursorImageLoadCursor)(event_loop.xconn.display, ximage);
(event_loop.xconn.xcursor.XcursorImageDestroy)(ximage);
Ok(Self {
inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }),
})
}
let cursor = event_loop
.xconn
.create_cursor_from_image(
cursor.0.width,
cursor.0.height,
cursor.0.hotspot_x,
cursor.0.hotspot_y,
&cursor.0.rgba,
)
.map_err(|err| os_error!(err))?;
Ok(Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) })
}
}
#[derive(Debug)]
struct CustomCursorInner {
xconn: Arc<XConnection>,
cursor: ffi::Cursor,
cursor: xproto::Cursor,
}
impl Drop for CustomCursorInner {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor);
}
self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok();
}
}

View File

@@ -67,16 +67,21 @@ pub struct FrameExtentsHeuristic {
}
impl FrameExtentsHeuristic {
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
pub fn surface_position(&self) -> (i32, i32) {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
(x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
(self.frame_extents.left as i32, self.frame_extents.top as i32)
} else {
(x, y)
(0, 0)
}
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
let (left, top) = self.surface_position();
(x - left, y - top)
}
pub fn surface_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
self.frame_extents.left.saturating_add(self.frame_extents.right) as _
@@ -98,7 +103,7 @@ impl XConnection {
self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
}
// This is adequate for inner_size
// This is adequate for surface_size
pub fn get_geometry(
&self,
window: xproto::Window,

View File

@@ -6,7 +6,6 @@ use x11rb::protocol::xkb;
use super::*;
pub const VIRTUAL_CORE_POINTER: u16 = 2;
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.

View File

@@ -19,7 +19,7 @@ impl<'a, T> XSmartPointer<'a, T> {
}
}
impl<'a, T> Deref for XSmartPointer<'a, T> {
impl<T> Deref for XSmartPointer<'_, T> {
type Target = T;
fn deref(&self) -> &T {
@@ -27,13 +27,13 @@ impl<'a, T> Deref for XSmartPointer<'a, T> {
}
}
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
impl<T> DerefMut for XSmartPointer<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<'a, T> Drop for XSmartPointer<'a, T> {
impl<T> Drop for XSmartPointer<'_, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,14 @@
use std::collections::HashMap;
use std::error::Error;
use std::ffi::c_int;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard};
use std::{fmt, ptr};
use rwh_06::HasDisplayHandle;
use x11rb::connection::Connection;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::render;
use x11rb::protocol::xproto::{self, ConnectionExt};
use x11rb::resource_manager;
use x11rb::xcb_ffi::XCBConnection;
@@ -18,7 +21,6 @@ use crate::window::CursorIcon;
/// A connection to an X server.
pub struct XConnection {
pub xlib: ffi::Xlib,
pub xcursor: ffi::Xcursor,
// TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together
// for some reason.
@@ -55,8 +57,18 @@ pub struct XConnection {
/// Atom for the XSettings screen.
xsettings_screen: Option<xproto::Atom>,
/// XRender format information.
render_formats: render::QueryPictFormatsReply,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, xproto::Cursor>>,
}
impl HasDisplayHandle for XConnection {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
unsafe impl Send for XConnection {}
@@ -69,7 +81,6 @@ impl XConnection {
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
// opening the libraries
let xlib = ffi::Xlib::open()?;
let xcursor = ffi::Xcursor::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
let xinput2 = ffi::XInput2::open()?;
@@ -118,15 +129,22 @@ impl XConnection {
tracing::warn!("error setting XSETTINGS; Xft options won't reload automatically")
}
// Start getting the XRender formats.
let formats_cookie = render::query_pict_formats(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Fetch atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
// Finish getting everything else.
let formats =
formats_cookie.reply().map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
xcursor,
xinput2,
display,
xcb: Some(xcb),
@@ -138,6 +156,7 @@ impl XConnection {
database: RwLock::new(database),
cursor_cache: Default::default(),
randr_version: (randr_version.major_version, randr_version.minor_version),
render_formats: formats,
xsettings_screen,
})
}
@@ -145,7 +164,7 @@ impl XConnection {
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.intern_atom(false, format!("_XSETTINGS_S{default_screen}").as_bytes())
.ok()?
.reply()
.ok()?
@@ -257,6 +276,36 @@ impl XConnection {
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
self.xsettings_screen
}
/// Get the data containing our rendering formats.
#[inline]
pub fn render_formats(&self) -> &render::QueryPictFormatsReply {
&self.render_formats
}
/// Do we need to do an endian swap?
#[inline]
pub fn needs_endian_swap(&self) -> bool {
#[cfg(target_endian = "big")]
let endian = xproto::ImageOrder::MSB_FIRST;
#[cfg(not(target_endian = "big"))]
let endian = xproto::ImageOrder::LSB_FIRST;
self.xcb_connection().setup().image_byte_order != endian
}
pub fn raw_display_handle(&self) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
let display_handle = rwh_06::XlibDisplayHandle::new(
// SAFETY: display will never be null
Some(
std::ptr::NonNull::new(self.display as *mut _)
.expect("X11 display should never be null"),
),
self.default_screen_index() as c_int,
);
Ok(display_handle.into())
}
}
impl fmt::Debug for XConnection {

View File

@@ -22,6 +22,7 @@ use self::apple as platform;
use self::linux as platform;
#[cfg(orbital_platform)]
use self::orbital as platform;
#[allow(unused_imports)]
pub use self::platform::*;
#[cfg(web_platform)]
use self::web as platform;

View File

@@ -1,4 +1,3 @@
use std::any::Any;
use std::cell::Cell;
use std::collections::VecDeque;
use std::sync::{mpsc, Arc, Mutex};
@@ -13,19 +12,24 @@ use orbclient::{
use smol_str::SmolStr;
use super::{
DeviceId, KeyEventExtra, MonitorHandle, OsError, PlatformSpecificEventLoopAttributes,
RedoxSocket, TimeSocket, WindowId, WindowProperties,
KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket,
WindowProperties,
};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, ExternalError, NotSupportedError};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{self, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use crate::platform_impl::Window;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow, WindowId,
};
fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
@@ -283,17 +287,10 @@ impl EventLoop {
// events.
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
let event_socket = Arc::new(
RedoxSocket::event()
.map_err(OsError::new)
.map_err(|error| EventLoopError::Os(os_error!(error)))?,
);
let event_socket =
Arc::new(RedoxSocket::event().map_err(|error| os_error!(format!("{error}")))?);
let wake_socket = Arc::new(
TimeSocket::open()
.map_err(OsError::new)
.map_err(|error| EventLoopError::Os(os_error!(error)))?,
);
let wake_socket = TimeSocket::open().map_err(|error| os_error!(format!("{error}")))?;
event_socket
.write(&syscall::Event {
@@ -301,8 +298,7 @@ impl EventLoop {
flags: syscall::EventFlags::EVENT_READ,
data: wake_socket.0.fd,
})
.map_err(OsError::new)
.map_err(|error| EventLoopError::Os(os_error!(error)))?;
.map_err(|error| os_error!(format!("{error}")))?;
Ok(Self {
windows: Vec::new(),
@@ -313,8 +309,7 @@ impl EventLoop {
redraws: Arc::new(Mutex::new(VecDeque::new())),
destroys: Arc::new(Mutex::new(VecDeque::new())),
event_socket,
wake_socket,
user_events_sender,
event_loop_proxy: Arc::new(EventLoopProxy { wake_socket, user_events_sender }),
},
user_events_receiver,
})
@@ -368,9 +363,8 @@ impl EventLoop {
key_without_modifiers = logical_key.clone();
}
let window_id = RootWindowId(window_id);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
device_id: None,
event: event::KeyEvent {
logical_key,
physical_key,
@@ -400,82 +394,64 @@ impl EventLoop {
EventOption::TextInput(TextInputEvent { character }) => {
app.window_event(
window_target,
RootWindowId(window_id),
window_id,
event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
);
app.window_event(
window_target,
RootWindowId(window_id),
window_id,
event::WindowEvent::Ime(Ime::Commit(character.into())),
);
},
EventOption::Mouse(MouseEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CursorMoved {
device_id: event::DeviceId(DeviceId),
position: (x, y).into(),
},
);
app.window_event(window_target, window_id, event::WindowEvent::PointerMoved {
device_id: None,
primary: true,
position: (x, y).into(),
source: event::PointerSource::Mouse,
});
},
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
app.device_event(
window_target,
event::DeviceId(DeviceId),
event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
);
app.device_event(window_target, None, event::DeviceEvent::PointerMotion {
delta: (dx as f64, dy as f64),
});
},
EventOption::Button(ButtonEvent { left, middle, right }) => {
while let Some((button, state)) = event_state.mouse(left, middle, right) {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseInput {
device_id: event::DeviceId(DeviceId),
state,
button,
},
);
app.window_event(window_target, window_id, event::WindowEvent::PointerButton {
device_id: None,
primary: true,
state,
position: dpi::PhysicalPosition::default(),
button: button.into(),
});
}
},
EventOption::Scroll(ScrollEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseWheel {
device_id: event::DeviceId(DeviceId),
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
},
);
app.window_event(window_target, window_id, event::WindowEvent::MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
});
},
EventOption::Quit(QuitEvent {}) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CloseRequested,
);
app.window_event(window_target, window_id, event::WindowEvent::CloseRequested);
},
EventOption::Focus(FocusEvent { focused }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Focused(focused),
);
app.window_event(window_target, window_id, event::WindowEvent::Focused(focused));
},
EventOption::Move(MoveEvent { x, y }) => {
app.window_event(
window_target,
RootWindowId(window_id),
window_id,
event::WindowEvent::Moved((x, y).into()),
);
},
EventOption::Resize(ResizeEvent { width, height }) => {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Resized((width, height).into()),
window_id,
event::WindowEvent::SurfaceResized((width, height).into()),
);
// Acknowledge resize after event loop.
@@ -484,12 +460,22 @@ impl EventLoop {
// TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
let event = if entered {
event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId) }
event::WindowEvent::PointerEntered {
device_id: None,
primary: true,
position: dpi::PhysicalPosition::default(),
kind: event::PointerKind::Mouse,
}
} else {
event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId) }
event::WindowEvent::PointerLeft {
device_id: None,
primary: true,
position: None,
kind: event::PointerKind::Mouse,
}
};
app.window_event(window_target, RootWindowId(window_id), event);
app.window_event(window_target, window_id, event);
},
other => {
tracing::warn!("unhandled event: {:?}", other);
@@ -511,7 +497,7 @@ impl EventLoop {
let mut creates = self.window_target.creates.lock().unwrap();
creates.pop_front()
} {
let window_id = WindowId { fd: window.fd as u64 };
let window_id = WindowId::from_raw(window.fd);
let mut buf: [u8; 4096] = [0; 4096];
let path = window.fpath(&mut buf).expect("failed to read properties");
@@ -519,10 +505,8 @@ impl EventLoop {
self.windows.push((window, EventState::default()));
let window_id = RootWindowId(window_id);
// Send resize event on create to indicate first size.
let event = event::WindowEvent::Resized((properties.w, properties.h).into());
let event = event::WindowEvent::SurfaceResized((properties.w, properties.h).into());
app.window_event(&self.window_target, window_id, event);
// Send moved event on create to indicate first position.
@@ -535,16 +519,16 @@ impl EventLoop {
let mut destroys = self.window_target.destroys.lock().unwrap();
destroys.pop_front()
} {
let window_id = RootWindowId(destroy_id);
app.window_event(&self.window_target, window_id, event::WindowEvent::Destroyed);
self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd);
app.window_event(&self.window_target, destroy_id, event::WindowEvent::Destroyed);
self.windows
.retain(|(window, _event_state)| WindowId::from_raw(window.fd) != destroy_id);
}
// Handle window events.
let mut i = 0;
// While loop is used here because the same window may be processed more than once.
while let Some((window, event_state)) = self.windows.get_mut(i) {
let window_id = WindowId { fd: window.fd as u64 };
let window_id = WindowId::from_raw(window.fd);
let mut event_buf = [0u8; 16 * mem::size_of::<orbclient::Event>()];
let count =
@@ -602,7 +586,7 @@ impl EventLoop {
} {
app.window_event(
&self.window_target,
RootWindowId(window_id),
window_id,
event::WindowEvent::RedrawRequested,
);
}
@@ -682,11 +666,11 @@ impl EventLoop {
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
wake_socket: Arc<TimeSocket>,
pub(super) wake_socket: TimeSocket,
}
impl EventLoopProxy {
pub fn wake_up(&self) {
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
@@ -695,15 +679,6 @@ impl EventLoopProxy {
}
}
impl Clone for EventLoopProxy {
fn clone(&self) -> Self {
Self {
user_events_sender: self.user_events_sender.clone(),
wake_socket: self.wake_socket.clone(),
}
}
}
impl Unpin for EventLoopProxy {}
pub struct ActiveEventLoop {
@@ -713,33 +688,26 @@ pub struct ActiveEventLoop {
pub(super) redraws: Arc<Mutex<VecDeque<WindowId>>>,
pub(super) destroys: Arc<Mutex<VecDeque<WindowId>>>,
pub(super) event_socket: Arc<RedoxSocket>,
pub(super) wake_socket: Arc<TimeSocket>,
user_events_sender: mpsc::SyncSender<()>,
pub(super) event_loop_proxy: Arc<EventLoopProxy>,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> event_loop::EventLoopProxy {
event_loop::EventLoopProxy {
event_loop_proxy: EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
wake_socket: self.wake_socket.clone(),
},
}
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(self.event_loop_proxy.clone())
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<crate::window::Window, crate::error::OsError> {
let window = crate::platform_impl::Window::new(self, window_attributes)?;
Ok(crate::window::Window { window })
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_: CustomCursorSource,
) -> Result<RootCustomCursor, ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
) -> Result<RootCustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
@@ -774,21 +742,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
self.exit.get()
}
fn owned_display_handle(&self) -> event_loop::OwnedDisplayHandle {
event_loop::OwnedDisplayHandle { platform: OwnedDisplayHandle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new());
@@ -796,15 +758,12 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
}
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::OrbitalDisplayHandle::new().into())
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}

View File

@@ -1,13 +1,11 @@
#![cfg(target_os = "redox")]
use std::fmt::{self, Display, Formatter};
use std::num::{NonZeroU16, NonZeroU32};
use std::str;
use std::sync::Arc;
use std::{fmt, str};
use smol_str::SmolStr;
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle};
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::keyboard::Key;
mod event_loop;
@@ -15,6 +13,11 @@ mod event_loop;
pub use self::window::Window;
mod window;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
struct RedoxSocket {
fd: usize,
}
@@ -96,47 +99,6 @@ impl TimeSocket {
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WindowId {
fd: u64,
}
impl WindowId {
pub const fn dummy() -> Self {
WindowId { fd: u64::MAX }
}
}
impl From<WindowId> for u64 {
fn from(id: WindowId) -> Self {
id.fd
}
}
impl From<u64> for WindowId {
fn from(fd: u64) -> Self {
Self { fd }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
DeviceId
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FingerId;
impl FingerId {
pub const fn dummy() -> Self {
FingerId
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
@@ -163,7 +125,7 @@ impl<'a> WindowProperties<'a> {
}
}
impl<'a> fmt::Display for WindowProperties<'a> {
impl fmt::Display for WindowProperties<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
@@ -173,26 +135,6 @@ impl<'a> fmt::Display for WindowProperties<'a> {
}
}
#[derive(Clone, Debug)]
pub struct OsError(Arc<syscall::Error>);
impl OsError {
fn new(error: syscall::Error) -> Self {
Self(Arc::new(error))
}
}
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.0.fmt(fmt)
}
}
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct MonitorHandle;

View File

@@ -1,14 +1,13 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use super::{
ActiveEventLoop, MonitorHandle, OsError, RedoxSocket, TimeSocket, WindowId, WindowProperties,
};
use super::event_loop::EventLoopProxy;
use super::{ActiveEventLoop, MonitorHandle, RedoxSocket, WindowProperties};
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::platform_impl::Fullscreen;
use crate::window::ImePurpose;
use crate::{error, window};
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{self, Fullscreen, ImePurpose, Window as CoreWindow, WindowId};
// These values match the values uses in the `window_new` function in orbital:
// https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs
@@ -25,14 +24,14 @@ pub struct Window {
window_socket: Arc<RedoxSocket>,
redraws: Arc<Mutex<VecDeque<WindowId>>>,
destroys: Arc<Mutex<VecDeque<WindowId>>>,
wake_socket: Arc<TimeSocket>,
event_loop_proxy: Arc<EventLoopProxy>,
}
impl Window {
pub(crate) fn new(
el: &ActiveEventLoop,
attrs: window::WindowAttributes,
) -> Result<Self, error::OsError> {
) -> Result<Self, RequestError> {
let scale = MonitorHandle.scale_factor();
let (x, y) = if let Some(pos) = attrs.position {
@@ -42,13 +41,13 @@ impl Window {
(-1, -1)
};
let (w, h): (u32, u32) = if let Some(size) = attrs.inner_size {
let (w, h): (u32, u32) = if let Some(size) = attrs.surface_size {
size.to_physical::<u32>(scale).into()
} else {
(1024, 768)
};
// TODO: min/max inner_size
// TODO: min/max surface_size
// Async by default.
let mut flag_str = ORBITAL_FLAG_ASYNC.to_string();
@@ -115,89 +114,97 @@ impl Window {
creates.push_back(window_socket.clone());
}
el.wake_socket.wake().unwrap();
el.event_loop_proxy.wake_socket.wake().unwrap();
Ok(Self {
window_socket,
redraws: el.redraws.clone(),
destroys: el.destroys.clone(),
wake_socket: el.wake_socket.clone(),
event_loop_proxy: el.event_loop_proxy.clone(),
})
}
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)
}
fn get_flag(&self, flag: char) -> Result<bool, error::ExternalError> {
fn get_flag(&self, flag: char) -> Result<bool, RequestError> {
let mut buf: [u8; 4096] = [0; 4096];
let path = self
.window_socket
.fpath(&mut buf)
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
let path = self.window_socket.fpath(&mut buf).map_err(|err| os_error!(format!("{err}")))?;
let properties = WindowProperties::new(path);
Ok(properties.flags.contains(flag))
}
fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> {
fn set_flag(&self, flag: char, value: bool) -> Result<(), RequestError> {
self.window_socket
.write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
.map_err(|err| os_error!(format!("{err}")))?;
Ok(())
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId { fd: self.window_socket.fd as u64 }
fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let handle = rwh_06::OrbitalWindowHandle::new({
let window = self.window_socket.fd as *mut _;
std::ptr::NonNull::new(window).expect("orbital fd should never be null")
});
Ok(rwh_06::RawWindowHandle::Orbital(handle))
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle)
fn raw_display_handle_rwh_06(&self) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new()))
}
}
impl CoreWindow for Window {
fn id(&self) -> WindowId {
WindowId::from_raw(self.window_socket.fd)
}
#[inline]
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
let mut v = VecDeque::with_capacity(1);
v.push_back(MonitorHandle);
v
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
Some(CoreMonitorHandle { inner: MonitorHandle })
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle)
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(vec![CoreMonitorHandle { inner: MonitorHandle }].into_iter())
}
#[inline]
pub fn scale_factor(&self) -> f64 {
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
Some(CoreMonitorHandle { inner: MonitorHandle })
}
#[inline]
fn scale_factor(&self) -> f64 {
MonitorHandle.scale_factor()
}
#[inline]
pub fn request_redraw(&self) {
fn request_redraw(&self) {
let window_id = self.id();
let mut redraws = self.redraws.lock().unwrap();
if !redraws.contains(&window_id) {
redraws.push_back(window_id);
self.wake_socket.wake().unwrap();
self.event_loop_proxy.wake_socket.wake().unwrap();
}
}
#[inline]
pub fn pre_present_notify(&self) {}
fn pre_present_notify(&self) {}
#[inline]
pub fn reset_dead_keys(&self) {
fn reset_dead_keys(&self) {
// TODO?
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
fn surface_position(&self) -> PhysicalPosition<i32> {
// TODO: adjust for window decorations
(0, 0).into()
}
#[inline]
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
let mut buf: [u8; 4096] = [0; 4096];
let path = self.window_socket.fpath(&mut buf).expect("failed to read properties");
let properties = WindowProperties::new(path);
@@ -205,20 +212,14 @@ impl Window {
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
// TODO: adjust for window decorations
self.inner_position()
}
#[inline]
pub fn set_outer_position(&self, position: Position) {
fn set_outer_position(&self, position: Position) {
// TODO: adjust for window decorations
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
self.window_socket.write(format!("P,{x},{y}").as_bytes()).expect("failed to set position");
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
fn surface_size(&self) -> PhysicalSize<u32> {
let mut buf: [u8; 4096] = [0; 4096];
let path = self.window_socket.fpath(&mut buf).expect("failed to read properties");
let properties = WindowProperties::new(path);
@@ -226,26 +227,30 @@ impl Window {
}
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let (w, h): (u32, u32) = size.to_physical::<u32>(self.scale_factor()).into();
self.window_socket.write(format!("S,{w},{h}").as_bytes()).expect("failed to set size");
None
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
fn outer_size(&self) -> PhysicalSize<u32> {
// TODO: adjust for window decorations
self.inner_size()
self.surface_size()
}
fn safe_area(&self) -> PhysicalInsets<u32> {
PhysicalInsets::new(0, 0, 0, 0)
}
#[inline]
pub fn set_min_inner_size(&self, _: Option<Size>) {}
fn set_min_surface_size(&self, _: Option<Size>) {}
#[inline]
pub fn set_max_inner_size(&self, _: Option<Size>) {}
fn set_max_surface_size(&self, _: Option<Size>) {}
#[inline]
pub fn title(&self) -> String {
fn title(&self) -> String {
let mut buf: [u8; 4096] = [0; 4096];
let path = self.window_socket.fpath(&mut buf).expect("failed to read properties");
let properties = WindowProperties::new(path);
@@ -253,84 +258,82 @@ impl Window {
}
#[inline]
pub fn set_title(&self, title: &str) {
fn set_title(&self, title: &str) {
self.window_socket.write(format!("T,{title}").as_bytes()).expect("failed to set title");
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
fn set_transparent(&self, transparent: bool) {
let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent);
}
#[inline]
pub fn set_blur(&self, _blur: bool) {}
fn set_blur(&self, _blur: bool) {}
#[inline]
pub fn set_visible(&self, visible: bool) {
fn set_visible(&self, visible: bool) {
let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible);
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
fn is_visible(&self) -> Option<bool> {
Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false))
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
fn set_surface_resize_increments(&self, _increments: Option<Size>) {}
#[inline]
pub fn set_resizable(&self, resizeable: bool) {
fn set_resizable(&self, resizeable: bool) {
let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable);
}
#[inline]
pub fn is_resizable(&self) -> bool {
fn is_resizable(&self) -> bool {
self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false)
}
#[inline]
pub fn set_minimized(&self, _minimized: bool) {}
fn set_minimized(&self, _minimized: bool) {}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
fn is_minimized(&self) -> Option<bool> {
None
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
fn set_maximized(&self, maximized: bool) {
let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized);
}
#[inline]
pub fn is_maximized(&self) -> bool {
fn is_maximized(&self) -> bool {
self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false)
}
#[inline]
pub(crate) fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {}
fn set_fullscreen(&self, _monitor: Option<Fullscreen>) {}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
fn fullscreen(&self) -> Option<Fullscreen> {
None
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
fn set_decorations(&self, decorations: bool) {
let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations);
}
#[inline]
pub fn is_decorated(&self) -> bool {
fn is_decorated(&self) -> bool {
!self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false)
}
#[inline]
pub fn set_window_level(&self, level: window::WindowLevel) {
fn set_window_level(&self, level: window::WindowLevel) {
match level {
window::WindowLevel::AlwaysOnBottom => {
let _ = self.set_flag(ORBITAL_FLAG_BACK, true);
@@ -346,36 +349,33 @@ impl Window {
}
#[inline]
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
#[inline]
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
#[inline]
pub fn set_ime_allowed(&self, _allowed: bool) {}
fn set_ime_allowed(&self, _allowed: bool) {}
#[inline]
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
fn set_ime_purpose(&self, _purpose: ImePurpose) {}
#[inline]
pub fn focus_window(&self) {}
fn focus_window(&self) {}
#[inline]
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
#[inline]
pub fn set_cursor(&self, _: Cursor) {}
fn set_cursor(&self, _: Cursor) {}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
fn set_cursor_position(&self, _: Position) -> Result<(), RequestError> {
Err(NotSupportedError::new("set_cursor_position is not supported").into())
}
#[inline]
pub fn set_cursor_grab(
&self,
mode: window::CursorGrabMode,
) -> Result<(), error::ExternalError> {
fn set_cursor_grab(&self, mode: window::CursorGrabMode) -> Result<(), RequestError> {
let (grab, relative) = match mode {
window::CursorGrabMode::None => (false, false),
window::CursorGrabMode::Confined => (true, false),
@@ -383,31 +383,26 @@ impl Window {
};
self.window_socket
.write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
.map_err(|err| os_error!(format!("{err}")))?;
self.window_socket
.write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
.map_err(|err| os_error!(format!("{err}")))?;
Ok(())
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
fn set_cursor_visible(&self, visible: bool) {
let _ = self.window_socket.write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes());
}
#[inline]
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
self.window_socket
.write(b"D")
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
fn drag_window(&self) -> Result<(), RequestError> {
self.window_socket.write(b"D").map_err(|err| os_error!(format!("{err}")))?;
Ok(())
}
#[inline]
pub fn drag_resize_window(
&self,
direction: window::ResizeDirection,
) -> Result<(), error::ExternalError> {
fn drag_resize_window(&self, direction: window::ResizeDirection) -> Result<(), RequestError> {
let arg = match direction {
window::ResizeDirection::East => "R",
window::ResizeDirection::North => "T",
@@ -419,59 +414,63 @@ impl Window {
window::ResizeDirection::West => "L",
};
self.window_socket
.write(format!("D,{}", arg).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
.write(format!("D,{arg}").as_bytes())
.map_err(|err| os_error!(format!("{err}")))?;
Ok(())
}
#[inline]
pub fn show_window_menu(&self, _position: Position) {}
fn show_window_menu(&self, _position: Position) {}
#[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let handle = rwh_06::OrbitalWindowHandle::new({
let window = self.window_socket.fd as *mut _;
std::ptr::NonNull::new(window).expect("orbital fd should never be null")
});
Ok(rwh_06::RawWindowHandle::Orbital(handle))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Orbital(rwh_06::OrbitalDisplayHandle::new()))
fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), RequestError> {
Err(NotSupportedError::new("set_cursor_hittest is not supported").into())
}
#[inline]
pub fn set_enabled_buttons(&self, _buttons: window::WindowButtons) {}
fn set_enabled_buttons(&self, _buttons: window::WindowButtons) {}
#[inline]
pub fn enabled_buttons(&self) -> window::WindowButtons {
fn enabled_buttons(&self) -> window::WindowButtons {
window::WindowButtons::all()
}
#[inline]
pub fn theme(&self) -> Option<window::Theme> {
fn theme(&self) -> Option<window::Theme> {
None
}
#[inline]
pub fn has_focus(&self) -> bool {
fn has_focus(&self) -> bool {
false
}
#[inline]
pub fn set_theme(&self, _theme: Option<window::Theme>) {}
fn set_theme(&self, _theme: Option<window::Theme>) {}
pub fn set_content_protected(&self, _protected: bool) {}
fn set_content_protected(&self, _protected: bool) {}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl Drop for Window {
@@ -481,6 +480,6 @@ impl Drop for Window {
destroys.push_back(self.id());
}
self.wake_socket.wake().unwrap();
self.event_loop_proxy.wake_socket.wake().unwrap();
}
}

View File

@@ -6,15 +6,13 @@ mod channel;
mod concurrent_queue;
mod dispatcher;
mod notifier;
mod waker;
mod wrapper;
use atomic_waker::AtomicWaker;
pub(crate) use atomic_waker::AtomicWaker;
use concurrent_queue::{ConcurrentQueue, PushError};
pub use self::abortable::{AbortHandle, Abortable, DropAbortHandle};
pub use self::channel::{channel, Receiver, Sender};
pub use self::dispatcher::{DispatchRunner, Dispatcher};
pub use self::notifier::{Notified, Notifier};
pub use self::waker::{Waker, WakerSpawner};
use self::wrapper::Wrapper;
pub(crate) use self::wrapper::Wrapper;

View File

@@ -1,117 +0,0 @@
use std::future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::Poll;
use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
struct Handler<T> {
value: T,
handler: fn(&T, bool),
}
#[derive(Clone)]
struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> {
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, bool)) -> Self {
let inner = Arc::new(Inner {
awoken: AtomicBool::new(false),
waker: AtomicWaker::new(),
closed: AtomicBool::new(false),
});
let handler = Handler { value, handler };
let sender = Sender(Arc::clone(&inner));
Self(Wrapper::new(
main_thread,
handler,
|handler, _| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, true);
},
{
let inner = Arc::clone(&inner);
move |handler| async move {
while future::poll_fn(|cx| {
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
inner.waker.register(cx.waker());
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(false);
}
Poll::Pending
}
}
})
.await
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, false);
}
}
},
sender,
|inner, _| {
inner.0.awoken.store(true, Ordering::Relaxed);
inner.0.waker.wake();
},
))
}
pub fn waker(&self) -> Waker<T> {
Waker(self.0.clone())
}
pub fn take(&self) -> bool {
debug_assert!(
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);
self.0.with_sender_data(|inner| inner.0.awoken.swap(false, Ordering::Relaxed))
}
}
impl<T> Drop for WakerSpawner<T> {
fn drop(&mut self) {
self.0.with_sender_data(|inner| {
inner.0.closed.store(true, Ordering::Relaxed);
inner.0.waker.wake();
});
}
}
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(())
}
}
impl<T> Clone for Waker<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
struct Inner {
awoken: AtomicBool,
waker: AtomicWaker,
closed: AtomicBool,
}

View File

@@ -542,8 +542,8 @@ fn from_rgba(
//
// We call `createImageBitmap()` before spawning the future,
// to not have to clone the image buffer.
let mut options = ImageBitmapOptions::new();
options.premultiply_alpha(PremultiplyAlpha::None);
let options = ImageBitmapOptions::new();
options.set_premultiply_alpha(PremultiplyAlpha::None);
let bitmap = JsFuture::from(
window
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)

View File

@@ -1,32 +1,12 @@
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(i32);
use crate::event::DeviceId;
impl DeviceId {
pub fn new(pointer_id: i32) -> Self {
Self(pointer_id)
}
pub const fn dummy() -> Self {
Self(-1)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FingerId {
pointer_id: i32,
primary: bool,
}
impl FingerId {
pub fn new(pointer_id: i32, primary: bool) -> Self {
Self { pointer_id, primary }
}
pub const fn dummy() -> Self {
Self { pointer_id: -1, primary: false }
}
pub fn is_primary(self) -> bool {
self.primary
pub(crate) fn mkdid(pointer_id: i32) -> Option<DeviceId> {
if let Ok(pointer_id) = u32::try_from(pointer_id) {
Some(DeviceId::from_raw(pointer_id as i64))
} else if pointer_id == -1 {
None
} else {
tracing::error!("found unexpected negative `PointerEvent.pointerId`: {pointer_id}");
None
}
}

View File

@@ -1,4 +1,4 @@
use super::{backend, event, window, HasMonitorPermissionFuture, MonitorPermissionFuture};
use super::{backend, HasMonitorPermissionFuture, MonitorPermissionFuture};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError};
use crate::event::Event;
@@ -10,8 +10,7 @@ pub(crate) mod runner;
mod state;
mod window_target;
pub(crate) use proxy::EventLoopProxy;
pub(crate) use window_target::{ActiveEventLoop, OwnedDisplayHandle};
pub(crate) use window_target::ActiveEventLoop;
pub struct EventLoop {
elw: ActiveEventLoop,

View File

@@ -1,17 +1,101 @@
use super::runner::WeakShared;
use crate::platform_impl::platform::r#async::Waker;
use std::future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::Poll;
#[derive(Clone)]
pub struct EventLoopProxy {
runner: Waker<WeakShared>,
use super::super::main_thread::MainThreadMarker;
use crate::event_loop::EventLoopProxyProvider;
use crate::platform_impl::web::event_loop::runner::WeakShared;
use crate::platform_impl::web::r#async::{AtomicWaker, Wrapper};
pub struct EventLoopProxy(Wrapper<WeakShared, Arc<State>, ()>);
struct State {
awoken: AtomicBool,
waker: AtomicWaker,
closed: AtomicBool,
}
impl EventLoopProxy {
pub fn new(runner: Waker<WeakShared>) -> Self {
Self { runner }
pub fn new(main_thread: MainThreadMarker, runner: WeakShared) -> Self {
let state = Arc::new(State {
awoken: AtomicBool::new(false),
waker: AtomicWaker::new(),
closed: AtomicBool::new(false),
});
Self(Wrapper::new(
main_thread,
runner,
|runner, _| {
let runner = runner.borrow();
let runner = runner.as_ref().unwrap();
if let Some(runner) = runner.upgrade() {
runner.send_proxy_wake_up(true);
}
},
{
let state = Arc::clone(&state);
move |runner| async move {
while future::poll_fn(|cx| {
if state.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
state.waker.register(cx.waker());
if state.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
if state.closed.load(Ordering::Relaxed) {
return Poll::Ready(false);
}
Poll::Pending
}
}
})
.await
{
let runner = runner.borrow();
let runner = runner.as_ref().unwrap();
if let Some(runner) = runner.upgrade() {
runner.send_proxy_wake_up(false);
}
}
}
},
state,
|state, _| {
state.awoken.store(true, Ordering::Relaxed);
state.waker.wake();
},
))
}
pub fn wake_up(&self) {
self.runner.wake();
pub fn take(&self) -> bool {
debug_assert!(
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);
self.0.with_sender_data(|state| state.awoken.swap(false, Ordering::Relaxed))
}
}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
self.0.with_sender_data(|state| {
state.closed.store(true, Ordering::Relaxed);
state.waker.wake();
});
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.0.send(())
}
}

View File

@@ -3,27 +3,25 @@ use std::collections::{HashSet, VecDeque};
use std::iter;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use js_sys::Function;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, Navigator, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
use super::super::event;
use super::super::main_thread::MainThreadMarker;
use super::super::monitor::MonitorHandler;
use super::super::DeviceId;
use super::backend;
use super::proxy::EventLoopProxy;
use super::state::State;
use crate::dpi::PhysicalSize;
use crate::event::{
DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause,
WindowEvent,
};
use crate::event::{DeviceEvent, ElementState, Event, RawKeyEvent, StartCause, WindowEvent};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::backend::EventListenerHandle;
use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner};
use crate::platform_impl::platform::backend::{EventListenerHandle, SafeAreaHandle};
use crate::platform_impl::platform::r#async::DispatchRunner;
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
@@ -41,7 +39,7 @@ type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
struct Execution {
main_thread: MainThreadMarker,
proxy_spawner: WakerSpawner<WeakShared>,
event_loop_proxy: Arc<EventLoopProxy>,
control_flow: Cell<ControlFlow>,
poll_strategy: Cell<PollStrategy>,
wait_until_strategy: Cell<WaitUntilStrategy>,
@@ -50,7 +48,7 @@ struct Execution {
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<EventWrapper>>,
id: RefCell<u32>,
id: Cell<usize>,
window: web_sys::Window,
navigator: Navigator,
document: Document,
@@ -59,6 +57,7 @@ struct Execution {
redraw_pending: RefCell<HashSet<WindowId>>,
destroy_pending: RefCell<VecDeque<WindowId>>,
pub(crate) monitor: Rc<MonitorHandler>,
safe_area: Rc<SafeAreaHandle>,
page_transition_event_handle: RefCell<Option<backend::PageTransitionEventHandle>>,
device_events: Cell<DeviceEvents>,
on_mouse_move: OnEventHandle<PointerEvent>,
@@ -144,12 +143,7 @@ impl Shared {
let document = window.document().expect("Failed to obtain document");
Shared(Rc::<Execution>::new_cyclic(|weak| {
let proxy_spawner =
WakerSpawner::new(main_thread, WeakShared(weak.clone()), |runner, local| {
if let Some(runner) = runner.upgrade() {
runner.send_proxy_wake_up(local);
}
});
let proxy_spawner = EventLoopProxy::new(main_thread, WeakShared(weak.clone()));
let monitor = MonitorHandler::new(
main_thread,
@@ -158,9 +152,11 @@ impl Shared {
WeakShared(weak.clone()),
);
let safe_area = SafeAreaHandle::new(&window, &document);
Execution {
main_thread,
proxy_spawner,
event_loop_proxy: Arc::new(proxy_spawner),
control_flow: Cell::new(ControlFlow::default()),
poll_strategy: Cell::new(PollStrategy::default()),
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
@@ -172,11 +168,12 @@ impl Shared {
window,
navigator,
document,
id: RefCell::new(0),
id: Cell::new(0),
all_canvases: RefCell::new(Vec::new()),
redraw_pending: RefCell::new(HashSet::new()),
destroy_pending: RefCell::new(VecDeque::new()),
monitor: Rc::new(monitor),
safe_area: Rc::new(safe_area),
page_transition_event_handle: RefCell::new(None),
device_events: Cell::default(),
on_mouse_move: RefCell::new(None),
@@ -287,7 +284,7 @@ impl Shared {
}
// chorded button event
let device_id = RootDeviceId(DeviceId::new(event.pointer_id()));
let device_id = event::mkdid(event.pointer_id());
if let Some(button) = backend::event::mouse_button(&event) {
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
@@ -298,7 +295,7 @@ impl Shared {
runner.send_event(Event::DeviceEvent {
device_id,
event: DeviceEvent::Button { button: button.to_id(), state },
event: DeviceEvent::Button { button: button.to_id().into(), state },
});
return;
@@ -311,7 +308,7 @@ impl Shared {
Event::DeviceEvent {
device_id,
event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y) },
event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) },
}
}));
}),
@@ -328,7 +325,7 @@ impl Shared {
if let Some(delta) = backend::event::mouse_scroll_delta(&window, &event) {
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: None,
event: DeviceEvent::MouseWheel { delta },
});
}
@@ -345,9 +342,9 @@ impl Shared {
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::new(event.pointer_id())),
device_id: event::mkdid(event.pointer_id()),
event: DeviceEvent::Button {
button: button.to_id(),
button: button.to_id().into(),
state: ElementState::Pressed,
},
});
@@ -364,9 +361,9 @@ impl Shared {
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::new(event.pointer_id())),
device_id: event::mkdid(event.pointer_id()),
event: DeviceEvent::Button {
button: button.to_id(),
button: button.to_id().into(),
state: ElementState::Released,
},
});
@@ -382,7 +379,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: None,
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Pressed,
@@ -400,7 +397,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: None,
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Released,
@@ -439,11 +436,11 @@ impl Shared {
// Generate a strictly increasing ID
// This is used to differentiate windows when handling events
pub fn generate_id(&self) -> u32 {
let mut id = self.0.id.borrow_mut();
*id += 1;
pub fn generate_id(&self) -> usize {
let id = self.0.id.get();
self.0.id.set(id.checked_add(1).expect("exhausted `WindowId`"));
*id
id
}
pub fn request_redraw(&self, id: WindowId) {
@@ -495,14 +492,8 @@ impl Shared {
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = queueMicrotask)]
fn queue_microtask(task: Function);
}
queue_microtask(
Closure::once_into_js({
self.window().queue_microtask(
&Closure::once_into_js({
let this = Rc::downgrade(&self.0);
move || {
if let Some(shared) = this.upgrade() {
@@ -663,7 +654,7 @@ impl Shared {
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
events.extend(
self.0
.proxy_spawner
.event_loop_proxy
.take()
.then_some(Event::UserWakeUp)
.map(EventWrapper::from),
@@ -828,8 +819,8 @@ impl Shared {
self.0.wait_until_strategy.get()
}
pub(crate) fn waker(&self) -> Waker<WeakShared> {
self.0.proxy_spawner.waker()
pub(crate) fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.0.event_loop_proxy
}
pub(crate) fn weak(&self) -> WeakShared {
@@ -839,6 +830,10 @@ impl Shared {
pub(crate) fn monitor(&self) -> &Rc<MonitorHandler> {
&self.0.monitor
}
pub(crate) fn safe_area(&self) -> &Rc<SafeAreaHandle> {
&self.0.safe_area
}
}
#[derive(Clone, Debug)]

View File

@@ -1,36 +1,28 @@
use std::any::Any;
use std::cell::Cell;
use std::clone::Clone;
use std::iter;
use std::rc::Rc;
use std::sync::Arc;
use web_sys::Element;
use super::super::monitor::MonitorPermissionFuture;
use super::super::{lock, KeyEventExtra};
use super::event::DeviceId;
use super::runner::{EventWrapper, WeakShared};
use super::window::WindowId;
use super::{backend, runner, EventLoopProxy};
use crate::error::{ExternalError, NotSupportedError};
use crate::event::{
DeviceId as RootDeviceId, ElementState, Event, FingerId as RootFingerId, KeyEvent, Touch,
TouchPhase, WindowEvent,
};
use super::runner::EventWrapper;
use super::{backend, runner};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{ElementState, Event, KeyEvent, TouchPhase, WindowEvent};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as RootOwnedDisplayHandle,
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::keyboard::ModifiersState;
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::platform_impl::platform::r#async::Waker;
use crate::platform_impl::web::event_loop::proxy::EventLoopProxy;
use crate::platform_impl::Window;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as RootWindow,
WindowId as RootWindowId,
};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId};
#[derive(Default)]
struct ModifiersShared(Rc<Cell<ModifiersState>>);
@@ -72,14 +64,14 @@ impl ActiveEventLoop {
}
pub fn generate_id(&self) -> WindowId {
WindowId(self.runner.generate_id())
WindowId::from_raw(self.runner.generate_id())
}
pub fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
CustomCursorFuture(CustomCursor::new_async(self, source.inner))
}
pub fn register(&self, canvas: &Rc<backend::Canvas>, id: WindowId) {
pub fn register(&self, canvas: &Rc<backend::Canvas>, window_id: WindowId) {
let canvas_clone = canvas.clone();
canvas.on_touch_start();
@@ -93,13 +85,13 @@ impl ActiveEventLoop {
let clear_modifiers = (!modifiers.get().is_empty()).then(|| {
modifiers.set(ModifiersState::empty());
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
}
});
runner.send_events(clear_modifiers.into_iter().chain(iter::once(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::Focused(false),
})));
});
@@ -109,7 +101,7 @@ impl ActiveEventLoop {
canvas.on_focus(move || {
if !has_focus.replace(true) {
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::Focused(true),
});
}
@@ -128,10 +120,8 @@ impl ActiveEventLoop {
if focused {
canvas.has_focus.set(true);
self.runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Focused(true),
})
self.runner
.send_event(Event::WindowEvent { window_id, event: WindowEvent::Focused(true) })
}
let runner = self.runner.clone();
@@ -141,18 +131,16 @@ impl ActiveEventLoop {
let modifiers_changed = (modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::KeyboardInput {
device_id,
device_id: None,
event: KeyEvent {
physical_key,
logical_key,
@@ -177,18 +165,16 @@ impl ActiveEventLoop {
let modifiers_changed = (modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let device_id = RootDeviceId(DeviceId::dummy());
runner.send_events(
iter::once(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::KeyboardInput {
device_id,
device_id: None,
event: KeyEvent {
physical_key,
logical_key,
@@ -207,80 +193,85 @@ impl ActiveEventLoop {
);
let has_focus = canvas.has_focus.clone();
canvas.on_cursor_leave({
canvas.on_pointer_leave({
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, pointer_id| {
move |active_modifiers, device_id, primary, position, kind| {
let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let pointer = pointer_id.map(|device_id| Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorLeft { device_id: RootDeviceId(device_id) },
});
if focus.is_some() || pointer.is_some() {
runner.send_events(focus.into_iter().chain(pointer))
}
runner.send_events(focus.into_iter().chain(iter::once(Event::WindowEvent {
window_id,
event: WindowEvent::PointerLeft {
device_id,
primary,
position: Some(position),
kind,
},
})))
}
});
canvas.on_cursor_enter({
canvas.on_pointer_enter({
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, pointer_id| {
move |active_modifiers, device_id, primary, position, kind| {
let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let pointer = pointer_id.map(|device_id| Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorEntered { device_id: RootDeviceId(device_id) },
});
if focus.is_some() || pointer.is_some() {
runner.send_events(focus.into_iter().chain(pointer))
}
runner.send_events(focus.into_iter().chain(iter::once(Event::WindowEvent {
window_id,
event: WindowEvent::PointerEntered { device_id, primary, position, kind },
})))
}
});
canvas.on_cursor_move(
canvas.on_pointer_move(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, pointer_id, events| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
move |device_id, events| {
runner.send_events(events.flat_map(
|(active_modifiers, primary, position, source)| {
let modifiers = (has_focus.get()
&& modifiers.get() != active_modifiers)
.then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(
active_modifiers.into(),
),
}
});
runner.send_events(modifiers.into_iter().chain(events.flat_map(|position| {
let device_id = RootDeviceId(pointer_id);
iter::once(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorMoved { device_id, position },
})
})));
modifiers.into_iter().chain(iter::once(Event::WindowEvent {
window_id,
event: WindowEvent::PointerMoved {
device_id,
primary,
position,
source,
},
}))
},
));
}
},
{
@@ -288,205 +279,83 @@ impl ActiveEventLoop {
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, device_id, finger_id, events| {
move |active_modifiers, device_id, primary, position, state, button| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
runner.send_events(modifiers.into_iter().chain(events.map(
|(location, force)| Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Touch(Touch {
finger_id: RootFingerId(finger_id),
device_id: RootDeviceId(device_id),
phase: TouchPhase::Moved,
force: Some(force),
location,
}),
runner.send_events(modifiers.into_iter().chain([Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id,
primary,
state,
position,
button,
},
)));
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers,
device_id,
position: crate::dpi::PhysicalPosition<f64>,
buttons,
button| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let device_id = RootDeviceId(device_id);
let state = if buttons.contains(button.into()) {
ElementState::Pressed
} else {
ElementState::Released
};
// A chorded button event may come in without any prior CursorMoved events,
// therefore we should send a CursorMoved event to make sure that the
// user code has the correct cursor position.
runner.send_events(modifiers.into_iter().chain([
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorMoved { device_id, position },
},
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::MouseInput { device_id, state, button },
},
]));
}]));
}
},
);
canvas.on_mouse_press(
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
canvas.on_pointer_press({
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, pointer_id, position, button| {
let modifiers = (modifiers.get() != active_modifiers).then(|| {
move |active_modifiers, device_id, primary, position, button| {
let modifiers = (modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
runner.send_events(modifiers.into_iter().chain(iter::once(Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id,
primary,
state: ElementState::Pressed,
position,
button,
},
})));
}
});
canvas.on_pointer_release({
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, device_id, primary, position, button| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let device_id: RootDeviceId = RootDeviceId(pointer_id);
// A mouse down event may come in without any prior CursorMoved events,
// therefore we should send a CursorMoved event to make sure that the
// user code has the correct cursor position.
runner.send_events(modifiers.into_iter().chain([
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorMoved { device_id, position },
},
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::MouseInput {
device_id,
state: ElementState::Pressed,
button,
},
},
]));
}
},
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, device_id, finger_id, location, force| {
let modifiers = (modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
runner.send_events(modifiers.into_iter().chain(iter::once(
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Touch(Touch {
finger_id: RootFingerId(finger_id),
device_id: RootDeviceId(device_id),
phase: TouchPhase::Started,
force: Some(force),
location,
}),
},
)))
}
},
);
canvas.on_mouse_release(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, pointer_id, position, button| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
let device_id: RootDeviceId = RootDeviceId(pointer_id);
// A mouse up event may come in without any prior CursorMoved events,
// therefore we should send a CursorMoved event to make sure that the
// user code has the correct cursor position.
runner.send_events(modifiers.into_iter().chain([
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::CursorMoved { device_id, position },
},
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::MouseInput {
device_id,
state: ElementState::Released,
button,
},
},
]));
}
},
{
let runner_touch = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers, device_id, finger_id, location, force| {
let modifiers =
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
runner_touch.send_events(modifiers.into_iter().chain(iter::once(
Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Touch(Touch {
finger_id: RootFingerId(finger_id),
device_id: RootDeviceId(device_id),
phase: TouchPhase::Ended,
force: Some(force),
location,
}),
},
)));
}
},
);
runner.send_events(modifiers.into_iter().chain(iter::once(Event::WindowEvent {
window_id,
event: WindowEvent::PointerButton {
device_id,
primary,
state: ElementState::Released,
position,
button,
},
})));
}
});
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
@@ -495,16 +364,16 @@ impl ActiveEventLoop {
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
modifiers.set(active_modifiers);
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
}
});
runner.send_events(modifiers_changed.into_iter().chain(iter::once(
Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::MouseWheel {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: None,
delta,
phase: TouchPhase::Moved,
},
@@ -512,25 +381,11 @@ impl ActiveEventLoop {
)));
});
let runner = self.runner.clone();
canvas.on_touch_cancel(move |device_id, finger_id, location, force| {
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Touch(Touch {
finger_id: RootFingerId(finger_id),
device_id: RootDeviceId(device_id),
phase: TouchPhase::Cancelled,
force: Some(force),
location,
}),
});
});
let runner = self.runner.clone();
canvas.on_dark_mode(move |is_dark_mode| {
let theme = if is_dark_mode { Theme::Dark } else { Theme::Light };
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::ThemeChanged(theme),
});
});
@@ -557,8 +412,8 @@ impl ActiveEventLoop {
if canvas.old_size() != new_size {
canvas.set_old_size(new_size);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::Resized(new_size),
window_id,
event: WindowEvent::SurfaceResized(new_size),
});
canvas.request_animation_frame();
}
@@ -573,7 +428,7 @@ impl ActiveEventLoop {
&& !(is_intersecting && canvas_clone.is_intersecting.get().is_none())
{
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
window_id,
event: WindowEvent::Occluded(!is_intersecting),
});
}
@@ -582,7 +437,7 @@ impl ActiveEventLoop {
});
let runner = self.runner.clone();
canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id)));
canvas.on_animation_frame(move || runner.request_redraw(window_id));
canvas.on_context_menu();
}
@@ -608,7 +463,10 @@ impl ActiveEventLoop {
}
pub(crate) fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
self.runner.monitor().is_extended().ok_or(NotSupportedError::new())
self.runner
.monitor()
.is_extended()
.ok_or(NotSupportedError::new("has_multiple_screens is not supported"))
}
pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
@@ -619,29 +477,29 @@ impl ActiveEventLoop {
self.runner.monitor().has_detailed_monitor_permission()
}
pub(crate) fn waker(&self) -> Waker<WeakShared> {
self.runner.waker()
pub(crate) fn event_loop_proxy(&self) -> Arc<EventLoopProxy> {
self.runner.event_loop_proxy().clone()
}
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> RootEventLoopProxy {
let event_loop_proxy = EventLoopProxy::new(self.waker());
RootEventLoopProxy { event_loop_proxy }
let event_loop_proxy = self.event_loop_proxy();
RootEventLoopProxy::new(event_loop_proxy)
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<crate::window::Window, crate::error::OsError> {
) -> Result<Box<dyn crate::window::Window>, RequestError> {
let window = Window::new(self, window_attributes)?;
Ok(RootWindow { window })
Ok(Box::new(window))
}
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<RootCustomCursor, ExternalError> {
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: CustomCursor::new(self, source.inner) })
}
@@ -689,21 +547,15 @@ impl RootActiveEventLoop for ActiveEventLoop {
self.runner.exiting()
}
fn owned_display_handle(&self) -> RootOwnedDisplayHandle {
RootOwnedDisplayHandle { platform: OwnedDisplayHandle }
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn as_any(&self) -> &dyn Any {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new());
@@ -711,15 +563,12 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
}
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone)]
pub(crate) struct OwnedDisplayHandle;
impl OwnedDisplayHandle {
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::WebDisplayHandle::new().into())
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}

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