Compare commits

...

116 Commits

Author SHA1 Message Date
Kirill Chibisov
9cbce055d3 Winit version 0.30.7 2024-12-22 23:00:41 +03:00
Kirill Chibisov
727583ffbf x11: fix KeyboardInput delivered twice with IME
The filtered events were still processed even though they shouldn't once
we know that they're filtered.

Fixes #4048.
2024-12-22 23:00:41 +03:00
Kirill Chibisov
82f0949aea Winit version 0.30.6 2024-12-21 09:05:36 +03:00
Kirill Chibisov
5d2151e893 chore: fix typos 2024-12-21 09:05:36 +03:00
daxpedda
6f22822025 Update minimum version of wasm-bindgen (#3860) 2024-12-21 09:05:36 +03:00
Mads Marquart
cf829a44e7 ci: fix cargo deny 2024-12-21 09:05:36 +03:00
dependabot[bot]
f2fb7dee3d build(deps): bump EmbarkStudios/cargo-deny-action
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-12-21 09:05:36 +03:00
Skip R.
836e717021 macOS: Fix checking for undocumented cursors (#4033)
`AnyClass::responds_to` delegates to `class_respondsToSelector`, a
function provided by the Objective-C runtime. However, at some point,
this began to return `false` for selectors referring to undocumented
cursors, despite the cursors remaining accessible via said selectors.
That this check fails prevents the cursors from being used.

We can instead send `respondsToSelector:` to the `NSCursor` class
itself. As an instance method, this is nominally impossible; however,
Apple grants an exemption[1] that permits class objects to perform
instance methods defined in the root class.

Checking for the undocumented cursors in this way gets them working
again, at least on macOS Sequoia 15.1.1.

[1]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-TPXREF120
2024-12-21 09:05:36 +03:00
Mads Marquart
1e44c354fe Fix MonitorHandle PartialEq and Hash on iOS (#4013) 2024-12-21 09:05:36 +03:00
Mads Marquart
1fb118031c 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-21 09:05:36 +03:00
Mads Marquart
d6ab3dcb86 chore: fix clippy lints 2024-12-21 09:05:36 +03:00
Rodrigo Rivas Costa
d828523b39 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-12-21 09:05:36 +03:00
Nico Burns
fccfd359db macOS: set activation policy by default if app is not bundled (#3961) 2024-12-21 09:05:36 +03:00
lucasmerlin
5c1cc652c4 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-12-21 09:05:36 +03:00
Piotr Podusowski
1d18a6ca57 android: use show_soft_input to summon the keyboard
Route it via the `Window::set_ime_allowed` like on iOS.
2024-12-21 09:05:36 +03:00
Mads Marquart
bab2d82fd3 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-12-21 09:05:36 +03:00
Matěj Laitl
bbab3d8e35 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-12-21 09:05:36 +03:00
Matěj Laitl
b02a70e6bb x11: iterate only visuals from the matching screen 2024-12-21 09:05:36 +03:00
AngelicosPhosphoros
9ceb8d4144 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-12-21 09:05:36 +03:00
Shane Celis
485ae90aae macOS: fix panic during drag_window
Return error from it instead of unwrapping.
2024-12-21 09:05:36 +03:00
Kirill Chibisov
107bf91a2d x11: don't forward key events to IME when it's disabled
Fixes #3815.
2024-12-21 09:05:36 +03:00
Mads Marquart
1bc405e526 macOS: Fix move event sometimes being triggered on resize (#3914) 2024-12-21 09:05:36 +03:00
Kirill Chibisov
685090021b chore: fix nightly CI on linux/ios
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-12-21 09:05:36 +03:00
purajit
cfe7eb8954 Prevent winit from overriding LSUIElement in package manifests (#3920) 2024-12-21 09:05:36 +03:00
Tarek Abdel Sater
838b2459a7 macOS: add option to explicitly hide menu/dock in Borderless (#3882) 2024-12-21 09:05:36 +03:00
John Nunley
f4a5ad9c22 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-12-21 09:05:36 +03:00
John Nunley
00ecd1533e 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-12-21 09:05:36 +03:00
Kirill Chibisov
114512bb32 Winit version 0.30.5 2024-08-08 21:26:00 +03:00
Kirill Chibisov
8c07319ced chore: drop libera link
It has low amount of users and not active, so better direct people
straight to matrix at this point.
2024-08-08 21:26:00 +03:00
daxpedda
fa3ec006e2 Add ActiveEventLoop::system_theme()
This also fixes macOS returning `None` in `Window::theme()` if no theme
override is set, instead it now returns the system theme.

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

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

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

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

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

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

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

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

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

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

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

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

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

* Only queue events if necessary

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

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

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

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-27 19:00:38 +04:00
Mads Marquart
cd6ec19300 Don't set the background color when initializing with transparency (#3657)
Setting the background color changes how the window title bar appears,
which is something that the application should customize itself if it
wants this behaviour (and also, it wasn't set when calling
`set_transparent`, so the behaviour wasn't consistent).
2024-04-27 19:00:38 +04:00
growfrow
61bd8172bd chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 19:00:38 +04:00
Kirill Chibisov
c04c113e7e chore: ensure that .cargo config is not published
Just in case, so the correct changelog will be rendered when pulling
the crate from the crates.io as archive and trying to build it.
2024-04-27 19:00:38 +04:00
Marijn Suijten
ce32a3024e android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 19:00:38 +04:00
John Nunley
1682703b5c bugfix(win32): Only return win handle on OK thread
On Windows, it is generally unsafe to use the HWND outside of the thread
that it originates from. In reality, the HWND is an index into a
thread-local table, so using it outside of the GUI thread can result in
another window being used instead, following by code unsoundness. This
is why the WindowHandle type is !Send. However, Window is Send and Sync,
which means we have to account for this.

Thus far the best solution seems to be to check if we are not in the GUI
thread. If we aren't, refuse the return the window handle.

For users who want to ensure the safety themselves, the unsafe API
was added.

Signed-off-by: John Nunley <dev@notgull.net>
2024-04-26 20:28:10 +04:00
Mads Marquart
bdd80c8af2 Add .git-blame-ignore-revs
The user has to explicitly opt into this, the file itself documents how
to do so.
2024-04-26 20:19:45 +04:00
Kirill Chibisov
7b0c7b6cb2 chore(rustfmt): use nightly (#2325)
Stable rustfmt lacks a lot of features resulting in worse formatted
code, thus use nightly formatter.
2024-04-26 17:11:44 +02:00
Marijn Suijten
7006c7ceca bugfix(android): Allow Volume* keys to be passed to the user 2024-04-23 21:51:23 +04:00
Kirill Chibisov
2491f2bbd6 windows: bump windows-sys to 0.52 2024-04-22 17:21:53 +04:00
tetra
babbb715c5 Windows: implement resize increments (#3623) 2024-04-21 15:05:41 +02:00
John Nunley
be79e8979a docs: Don't reference EventLoopBuilderExtUnix
This replaces a reference to EventLoopBuilderExtUnix in a panic
message with EventLoopBuilderExtX11 or EventLoopBuilderExtWayland.

Closes #3488

Signed-off-by: John Nunley <dev@notgull.net>
2024-04-20 19:45:07 -07:00
Kirill Chibisov
9ab4c03e89 wayland: fix CSD decorations glitch when closing
In rare cases destroying subsurfaces before the main surface could
result in a frame where the window is still shown, but decorations
got hidden, right before the window itself disappears.
2024-04-19 14:40:00 +04:00
daxpedda
4f47a4e793 Update dev-dependencies (#3641) 2024-04-18 21:52:38 +02:00
daxpedda
c15fa6e433 Re-introduce Web examples (#3637) 2024-04-18 19:43:39 +02:00
Kirill Chibisov
24faacf497 docs: fix release steps in CONTRIBUTING.md
The steps were rebased incorrectly.

Fixes: 575d978202 (docs: Rework CONTRIBUTING guidelines)
2024-04-18 20:27:31 +04:00
Mads Marquart
080556f2c8 Fix icrate's clippy customization 2024-04-18 20:04:42 +04:00
Mads Marquart
259e868c05 Update objc2 crates (#3634)
Changes relevant to Winit:
- `icrate` has been deprecated in favour of separate crates per
  framework, in our case `objc2-foundation` and `objc2-app-kit` (and in
  the future `objc2-ui-kit` on iOS).
- Moved `MainThreadMarker::run_on_main` to free-standing function
  `run_on_main`.
- Changed how features work, this should result in less code that we
  need to compile.
- Enums are now real structs instead of type-aliases and free constants.
2024-04-18 17:34:19 +02:00
Kirill Chibisov
575d978202 docs: Rework CONTRIBUTING guidelines
Give better recommendations for contributors on how to propose start the
work, write commit messages, and handle review by explicitly stating
expectations, however make it clear that maintainers will help you if
you have issues.

Write down the guidelines for maintainers on how they should handle
contributions and interact with other maintainers to help maintaining
good code quality level by listing maintainers responsibilities and
providing suggestions on how to handle various situations.

Co-authored-by: John Nunley <dev@notgull.net>
Signed-off-by: John Nunley <dev@notgull.net>
2024-04-16 17:06:36 -07:00
Bruce Mitchener
7dd7dc1fc8 example: Fix typo: "fosus" -> "focus" (#3629) 2024-04-15 23:59:58 +02:00
Bruce Mitchener
df7c496a5d chore: more concise Debug output for WindowId 2024-04-14 21:06:18 +04:00
Kirill Chibisov
0086d7153b wayland: bump sctk-adwaita to 0.9.0
This is a breaking change, because the system versions of the libraries
used by sctk-adwaita were changed. Such changes cascade through all
the deps, so all libraries using winit MUST do a breaking change.
2024-04-14 20:38:20 +04:00
Bruce Mitchener
b79acd8a5a ci: Update all jobs to actions/checkout@v4
Updates the checkout action to the latest version
2024-04-13 20:50:41 -07:00
Bruce Mitchener
c2951e0194 docs: Fix copy/paste error in changelog
Due to a copy/paste error a comment in the migration in the changelog mentioned
"window events" in a context that was clearly meant for device events. This
commit fixes this issue.
2024-04-13 18:12:39 -07:00
Kirill Chibisov
8998e36994 chore(changelog): write migrations for 0.30
Add a migrations for the big API changes showcasing how to upgrade
some common snippets of old code to new APIs.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-12 21:55:44 +04:00
Kirill Chibisov
5a7169c7a6 chore(changelog): use 'keep a changelog' format
Links: https://keepachangelog.com/en/1.0.0/
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-12 21:38:50 +04:00
Mads Marquart
4cd6877e8e Fix CI failing when updating typos to 1.20.3 (#3620) 2024-04-03 00:38:55 +02:00
Colin Kinloch
44aabdddcc example: add an example of request_inner_size
Add the shortcut "Alt + R" to swap the size of the window axes in
window example.
2024-03-28 23:39:19 +04:00
James Tucker
c4415009c0 windows: remove github URLs from binaries
There are  AV rules out there which cause almost any
program that contains github URLs to become marked as malware.

While those rules are spurious, they're years old, and AV vendors have a
very poor reputation at appropriately following up with these problems.

Remove these strings from the panic data present in the binary
prevents binaries linking the winit from being spuriously marked as
Trojan:Win32/Wacatac.B!ml.
2024-03-28 22:45:34 +04:00
Mads Marquart
63a7c02492 Consistently emit extra mouse motion events (#3601)
In particular, we don't want to emit those events inside of
`pressureChangeWithEvent:`, since the mouse motion value is sometimes
outdated.

Additionally, we want to ensure the events have been emitted during
other gestures.

Fixes https://github.com/rust-windowing/winit/issues/3516
2024-03-28 19:18:03 +01:00
John Nunley
7b0ef160fc chore: silence clippy
It appears that clippy's dead code detection has gotten better. This
commit fixes winit's code to match.

Signed-off-by: John Nunley <dev@notgull.net>
2024-03-27 12:20:21 +04:00
wannacu
962241e2a0 bugfix(x11): Fix window position calculation overflow during dragging
Closes #3594

Signed-off-by: wannacu <wannacu2049@gmail.com>
2024-03-25 21:16:29 -07:00
Mads Marquart
3efa6d855d chore: Fix Clippy after Rust 1.77
- There are some thread_local instances that can be made constant.
- An inner attribute can be moved to an outer one.
2024-03-21 19:32:20 -07:00
Smith Chang
9067426dca chore: remove repetitive words (#3599) 2024-03-21 18:44:23 +01:00
Fredrik Fornwall
ba10c35240 fix(windows): configure hidden and grabbed cursor
This fixes issues where a hidden and grabbed cursor could leave the
window and become visible on top of the windows taskbar (and potentially
leave the window altogether if the taskbar is clicked) under at least
two occasions:

    - When a window is overlapping the taskbar.
    - When a window is maximized and Automatically hide the taskbar has
      been enabled.

This approach of confining the cursor to the center of the window is
used in SDL.
2024-03-18 23:50:40 +04:00
daxpedda
baf10de958 Prefer [foo][bar] over [foo](bar) for intra-doc links (#3191) 2024-03-16 10:22:29 +01:00
194 changed files with 7903 additions and 8430 deletions

View File

@@ -1,6 +1,3 @@
[alias]
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
# Allow rust-analyzer and local `cargo doc` invocations to pick up unreleased changelog entries
#
# Note that these flags are (intentionally) not included when building from the downloaded crate.

4
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,4 @@
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
# chore(rustfmt): use nightly
7b0c7b6cb2c62767ca0c73c857b299883f55a883

8
.github/CODEOWNERS vendored
View File

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

View File

@@ -10,8 +10,8 @@ jobs:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/checkout-action@v1
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- name: Check Formatting
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: taiki-e/checkout-action@v1
- uses: taiki-e/install-action@v2
with:
tool: typos-cli
@@ -88,7 +88,7 @@ jobs:
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: actions/checkout@v3
- uses: taiki-e/checkout-action@v1
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
@@ -220,9 +220,24 @@ jobs:
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
- uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
swc:
name: Minimize JavaScript
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1
- name: Install SWC
run: sudo npm i -g @swc/cli
- name: Run SWC
run: |
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
- name: Check for diff
run: |
[[ -z $(git status -s) ]]

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:

3
.gitignore vendored
View File

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

12
.swcrc Normal file
View File

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

View File

@@ -1,52 +1,122 @@
# Winit Contributing Guidelines
# Contribution Guidelines
## Scope
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
consider whether or not it's directly related to window creation or input handling. If it isn't, it
may be worth creating a separate crate that extends Winit's API to add that functionality.
This document contains guidelines for contributing code to winit. It has to be
followed in order for your patch to be approved and applied.
## Contributing
## Reporting an issue
Anyone can contribute to winit, however given that it's a cross platform
windowing toolkit getting certain changes incorporated could be challenging.
When reporting an issue, in order to help the maintainers understand what the problem is, please make
your description of the issue as detailed as possible:
To save your time it's wise to check already opened [pull requests][prs] and
[issues][issues]. In general, bug fixes and missing implementations are always
accepted, however larger new API proposals should go into the issue first. When
in doubt contact us on [Matrix][matrix] or via opening an issue.
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
### Submitting your work and handling review
## Making a pull request
All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access
to the branch" checkbox.
When making a code contribution to winit, before opening your pull request, please make sure that:
#### Handling review
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the current changelog if the introduced change is relevant to winit users.
During the review process certain events could require an action from your side,
common patterns and reactions are described below.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
handle those for you when merging (see below).
- if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
_Event:_ The CI fails to build, but it looks like it is not your fault. Not
communicating so could result in maintainers not looking into your patch,
since they may assume that you're still working on it.\
_Desired behaviour:_ Write a message saying roughly the following "The CI
failure is unrelated", so that the maintainers will fix it for you.
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
is that a PR must be approved by at least two maintainers of winit before being merged, including
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
_Event:_ Maintainer requested changes to your PR.\
_Desired behavior:_ Once you address the request, you should re-request a review
with GitHub's UI. If you don't agree with what maintainer suggested, you
should state your objections and re-request the review. That will indicate that
the ball is on maintainer's side.
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in the
`changelog` module (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
_Event:_ You've opened a PR, but maintainer shortly after commented that they
want to work on that themselves.\
_Desired behavior:_ Discuss with the maintainer regarding their plans if they
were not outlined in the initial response, because such response means that they
are not interested in reviewing your code. Such thing could happen when
underestimating complexity of the task you're solving or when your patch
mandate certain downstream designs. In general, the maintainer will likely
close your PR in order to prevent work being done on it.
[prs]: https://github.com/rust-windowing/winit/pulls
[issues]: https://github.com/rust-windowing/winit/issues
[matrix]: https://matrix.to/#/#rust-windowing:matrix.org
## Maintainers
The current maintainers for each platform are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
Winit has plenty of maintainers with different backgrounds, different time
available to work on Winit, and reasons to be winit maintainer in the first
place. To ensure that Winit's code quality does not decrease over time and to
make it easier to teach new maintainers the "winit way of doing things" the
common policies and routines are defined in this section.
## Release process
The current maintainers for each platform are listed in [this file][CODEOWNERS].
### Contributions handling
The maintainers must ensure that the external contributions meet Winit's
quality standards. If it's not, it **is the maintainer's responsibility** to
bring it on par, which includes:
- Ensure that formatting is consistent and `CHANGELOG` messages are clear
for the end users.
- Improve the commit message, so it'll be easier for other maintainers to
understand the motivation without going through all the discussions on the
particular patch/PR.
- Ensure that the proposed patch doesn't break platform parity. If the
breakage is desired by contributor, an issue should be opened to discuss
with other maintainers before merging.
- Always fix CI issues before merging if they don't originate from the
submitted work.
However, maintainers should give some leeway for external contributors, so they
don't feel discouraged contributing, for example:
- Suggest a patch to resolve style issues, if it's the only issue with the
submitted work. Keep in mind that pushing the resolution yourself is not
desired, because the contributor might not agree with what you did.
- Be more explicit on how things should be done if you don't like the
approach.
- Suggest to finish the PR for them if they're absent for a while and you need
the proposed changes to move forward with something. In such cases the
maintainer must preserve attribution with `Co-authored-by`, `Suggested-by`,
or keep the original committer.
- Rebase their work for them when massive changes to the Winit codebase were
introduced.
When reviewing code of other maintainers all of the above is on the maintainer
who submitted the patch. Interested maintainers could help push the work over
the finish line, but teaching other maintainers should be preferred.
For a _regular_ contributor to winit, the maintainer should slowly start
requiring contributor to match *maintainer* quality standards when writing
patches and commit messages.
### Contributing
When submitting a patch, the maintainer should follow the general contributing
guidelines, however greater attention to detail is expected in this case.
To make life simpler for other maintainers it's suggested to create your branch
under the project repository instead of your own fork. The naming scheme is
`github_user_name/branch_name`. Doing so will make your work easier to rebase
for other maintainers when you're absent.
### Administrative Actions
Some things (such as changing required CI steps, adding contributors, ...)
require administrative permissions. If you don't have those, ask about the
change in an issue. If you have the permissions, discuss it with at least one
other admin before making the change.
### Release process
Given that winit is a widely used library, we should be able to make a patch
releases at any time we want without blocking the development of new features.
@@ -58,7 +128,8 @@ The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release
3. Update released `cfg_attr` in `src/changelog/mod.rs` to `v0.2.md`
4. Move entries from `src/changelog/unreleased.md` into `src/changelog/v0.2.md`
4. Move entries from `src/changelog/unreleased.md` into
`src/changelog/v0.2.md`
5. In the branch, the version is bumped to `v0.2.0`
6. The new commit in the branch is tagged `v0.2.0`
7. The version is pushed to crates.io
@@ -70,3 +141,5 @@ When doing a patch release, the process is similar:
2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 4-9 of the regular release example
[CODEOWNERS]: .github/CODEOWNERS

View File

@@ -1,7 +1,10 @@
[package]
name = "winit"
version = "0.29.15"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
version = "0.30.7"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
]
description = "Cross-platform window creation library."
keywords = ["windowing"]
readme = "README.md"
@@ -11,6 +14,17 @@ rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
[package.metadata.docs.rs]
features = [
@@ -45,7 +59,15 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"]
wayland = [
"wayland-client",
"wayland-backend",
"wayland-protocols",
"wayland-protocols-plasma",
"sctk",
"ahash",
"memmap2",
]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
@@ -59,94 +81,146 @@ rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies]
cfg_aliases = "0.2.0"
cfg_aliases = "0.2.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" }
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true }
rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = [
"std",
], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
"std",
], optional = true }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
tracing = { version = "0.1.40", default_features = false }
tracing = { version = "0.1.40", default-features = false }
[dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
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"] }
winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies]
softbuffer = { version = "0.3.0", default-features = false, features = ["x11", "x11-dlopen", "wayland", "wayland-dlopen"] }
softbuffer = { version = "0.4.0", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
"wayland-dlopen",
] }
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0"
ndk = { version = "0.8.0", default-features = false }
ndk-sys = "0.5.0"
android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.5.0"
objc2 = "0.5.2"
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate]
version = "0.1.0"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSAttributedString",
"Foundation_NSMutableAttributedString",
"Foundation_NSData",
"Foundation_NSDictionary",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSNumber",
"AppKit",
"AppKit_NSAppearance",
"AppKit_NSApplication",
"AppKit_NSBitmapImageRep",
"AppKit_NSButton",
"AppKit_NSColor",
"AppKit_NSControl",
"AppKit_NSCursor",
"AppKit_NSEvent",
"AppKit_NSGraphicsContext",
"AppKit_NSImage",
"AppKit_NSImageRep",
"AppKit_NSMenu",
"AppKit_NSMenuItem",
"AppKit_NSPasteboard",
"AppKit_NSResponder",
"AppKit_NSScreen",
"AppKit_NSTextInputContext",
"AppKit_NSView",
"AppKit_NSWindow",
"AppKit_NSWindowTabGroup",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
]
[target.'cfg(target_os = "ios")'.dependencies.icrate]
version = "0.1.0"
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
version = "0.2.2"
features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSGraphicsContext",
"NSImage",
"NSImageRep",
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
]
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"dispatch",
"Foundation",
"Foundation_NSArray",
"Foundation_NSString",
"Foundation_NSProcessInfo",
"Foundation_NSThread",
"Foundation_NSSet",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSString",
"NSProcessInfo",
"NSThread",
"NSSet",
]
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
version = "0.2.2"
features = [
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
"UITouch",
"UITraitCollection",
"UIView",
"UIViewController",
"UIWindow",
]
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.48"
version = "0.52.0"
features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
@@ -158,6 +232,7 @@ features = [
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -177,84 +252,110 @@ features = [
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3"
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true }
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true }
rustix = { version = "0.38.4", default-features = false, features = [
"std",
"system",
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.5", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.4", optional = true }
wayland-protocols = { version = "0.32.2", features = [
"staging",
], optional = true }
wayland-protocols-plasma = { version = "0.3.2", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code",
"dl-libxcb",
"randr",
"resource_manager",
"xinput",
"xkb",
], optional = true }
xkbcommon-dl = "0.4.2"
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Url',
]
[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.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] }
console_error_panic_hook = "0.1"
tracing-web = "0.1"
[[example]]
doc-scrape-examples = true
@@ -262,10 +363,7 @@ name = "window"
[workspace]
resolver = "2"
members = [
"dpi",
"run-wasm",
]
members = ["dpi"]
[workspace.package]
rust-version = "1.70.0"

View File

@@ -179,7 +179,7 @@ Legend:
|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 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** |

View File

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

View File

@@ -1,10 +1,10 @@
use cfg_aliases::cfg_aliases;
fn main() {
// The script doesn't depend on our code
// The script doesn't depend on our code.
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases
// Setup cfg aliases.
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
@@ -21,4 +21,7 @@ fn main() {
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
orbital_platform: { redox },
}
// Winit defined cfgs.
println!("cargo:rustc-check-cfg=cfg(unreleased_changelogs)");
}

View File

@@ -10,6 +10,6 @@ disallowed-methods = [
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
]

View File

@@ -1,15 +1,20 @@
# https://embarkstudios.github.io/cargo-deny/
# https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# cargo update && cargo deny --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph]
all-features = true
exclude-dev = true
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown" },
{ triple = "wasm32-unknown-unknown", features = [
"atomics",
] },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
@@ -18,46 +23,52 @@ targets = [
{ triple = "x86_64-unknown-redox" },
]
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[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-3.0", # https://spdx.org/licenses/Unicode-3.0.html
]
confidence-threshold = 1.0
private = { ignore = true }
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
{ crate = "raw-window-handle", reason = "we depend on multiple behind features" },
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
]
skip-tree = []
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
[bans.build]
include-archives = true
interpreted = "deny"
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
[[bans.build.bypass]]
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.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)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

View File

@@ -35,9 +35,9 @@
//!
//! ### Position and Size types
//!
//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual pixels on the
//! device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types correspond to the physical pixels
//! divided by the scale factor.
//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual
//! pixels on the device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types
//! correspond to the physical pixels divided by the scale factor.
//!
//! The position and size types are generic over their exact pixel type, `P`, to allow the
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
@@ -52,19 +52,14 @@
//!
//! This crate provides the following Cargo features:
//!
//! * `serde`: Enables serialization/deserialization of certain types with
//! [Serde](https://crates.io/crates/serde).
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//!
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![forbid(unsafe_code)]
#[cfg(feature = "serde")]
@@ -120,9 +115,9 @@ impl Pixel for f64 {
/// Checks that the scale factor is a normal positive `f64`.
///
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
/// otherwise, you risk panics.
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing
/// scale factors from anywhere other than winit, it's recommended to validate them using this
/// function before passing them to winit; otherwise, you risk panics.
#[inline]
pub fn validate_scale_factor(scale_factor: f64) -> bool {
scale_factor.is_sign_positive() && scale_factor.is_normal()
@@ -134,12 +129,12 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool {
pub struct LogicalUnit<P>(pub P);
impl<P> LogicalUnit<P> {
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
/// Represents a minimum logical unit of [`f64::MAX`].
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
/// Represents a logical unit of `0_f64`.
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
#[inline]
pub const fn new(v: P) -> Self {
@@ -228,12 +223,12 @@ impl<P: Pixel> From<LogicalUnit<P>> for f64 {
pub struct PhysicalUnit<P>(pub P);
impl<P> PhysicalUnit<P> {
/// Represents a maximum physical unit that is equal to [`f64::MAX`].
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
/// Represents a minimum physical unit of [`f64::MAX`].
pub const MIN: LogicalUnit<f64> = LogicalUnit::new(f64::MIN);
/// Represents a physical unit of `0_f64`.
pub const ZERO: LogicalUnit<f64> = LogicalUnit::new(0.0);
/// Represents a maximum physical unit that is equal to [`f64::MAX`].
pub const MAX: LogicalUnit<f64> = LogicalUnit::new(f64::MAX);
#[inline]
pub const fn new(v: P) -> Self {
@@ -322,12 +317,12 @@ pub enum PixelUnit {
}
impl PixelUnit {
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX));
/// Represents a minimum logical unit of [`f64::MAX`].
pub const MIN: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MIN));
/// Represents a logical unit of `0_f64`.
pub const ZERO: PixelUnit = PixelUnit::Logical(LogicalUnit::new(0.0));
/// Represents a maximum logical unit that is equal to [`f64::MAX`].
pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX));
pub fn new<S: Into<PixelUnit>>(unit: S) -> PixelUnit {
unit.into()
@@ -400,10 +395,7 @@ impl<P: Pixel> LogicalPosition<P> {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
LogicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
LogicalPosition { x: self.x.cast(), y: self.y.cast() }
}
}
@@ -479,10 +471,7 @@ impl<P: Pixel> PhysicalPosition<P> {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
PhysicalPosition {
x: self.x.cast(),
y: self.y.cast(),
}
PhysicalPosition { x: self.x.cast(), y: self.y.cast() }
}
}
@@ -558,10 +547,7 @@ impl<P: Pixel> LogicalSize<P> {
#[inline]
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
LogicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
LogicalSize { width: self.width.cast(), height: self.height.cast() }
}
}
@@ -599,10 +585,7 @@ impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
mint::Vector2 { x: s.width, y: s.height }
}
}
@@ -637,10 +620,7 @@ impl<P: Pixel> PhysicalSize<P> {
#[inline]
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
PhysicalSize {
width: self.width.cast(),
height: self.height.cast(),
}
PhysicalSize { width: self.width.cast(), height: self.height.cast() }
}
}
@@ -678,10 +658,7 @@ impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 {
x: s.width,
y: s.height,
}
mint::Vector2 { x: s.width, y: s.height }
}
}
@@ -846,12 +823,7 @@ mod tests {
macro_rules! assert_approx_eq {
($a:expr, $b:expr $(,)?) => {
assert!(
($a - $b).abs() < 0.001,
"{} is not approximately equal to {}",
$a,
$b
);
assert!(($a - $b).abs() < 0.001, "{} is not approximately equal to {}", $a, $b);
};
}
@@ -970,14 +942,8 @@ mod tests {
assert_eq!(log_unit.to_physical::<u32>(1.0), PhysicalUnit::new(1));
assert_eq!(log_unit.to_physical::<u32>(2.0), PhysicalUnit::new(2));
assert_eq!(log_unit.cast::<u32>(), LogicalUnit::new(1));
assert_eq!(
log_unit,
LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0)
);
assert_eq!(
log_unit,
LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0)
);
assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0));
assert_eq!(log_unit, LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0));
assert_eq!(LogicalUnit::from(2.0), LogicalUnit::new(2.0));
let x: f64 = log_unit.into();
@@ -986,14 +952,8 @@ mod tests {
#[test]
fn test_physical_unit() {
assert_eq!(
PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0),
PhysicalUnit::new(1)
);
assert_eq!(
PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5),
PhysicalUnit::new(1)
);
assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0), PhysicalUnit::new(1));
assert_eq!(PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5), PhysicalUnit::new(1));
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0,));
assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0));
@@ -1007,22 +967,10 @@ mod tests {
assert_eq!(log_pos.to_physical::<u32>(1.0), PhysicalPosition::new(1, 2));
assert_eq!(log_pos.to_physical::<u32>(2.0), PhysicalPosition::new(2, 4));
assert_eq!(log_pos.cast::<u32>(), LogicalPosition::new(1, 2));
assert_eq!(
log_pos,
LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_pos,
LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0)
);
assert_eq!(
LogicalPosition::from((2.0, 2.0)),
LogicalPosition::new(2.0, 2.0)
);
assert_eq!(
LogicalPosition::from([2.0, 3.0]),
LogicalPosition::new(2.0, 3.0)
);
assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(1.0, 2.0), 1.0));
assert_eq!(log_pos, LogicalPosition::from_physical(PhysicalPosition::new(2.0, 4.0), 2.0));
assert_eq!(LogicalPosition::from((2.0, 2.0)), LogicalPosition::new(2.0, 2.0));
assert_eq!(LogicalPosition::from([2.0, 3.0]), LogicalPosition::new(2.0, 3.0));
let x: (f64, f64) = log_pos.into();
assert_eq!(x, (1.0, 2.0));
@@ -1040,14 +988,8 @@ mod tests {
PhysicalPosition::from_logical(LogicalPosition::new(2.0, 4.0), 0.5),
PhysicalPosition::new(1, 2)
);
assert_eq!(
PhysicalPosition::from((2.0, 2.0)),
PhysicalPosition::new(2.0, 2.0)
);
assert_eq!(
PhysicalPosition::from([2.0, 3.0]),
PhysicalPosition::new(2.0, 3.0)
);
assert_eq!(PhysicalPosition::from((2.0, 2.0)), PhysicalPosition::new(2.0, 2.0));
assert_eq!(PhysicalPosition::from([2.0, 3.0]), PhysicalPosition::new(2.0, 3.0));
let x: (f64, f64) = PhysicalPosition::new(1, 2).into();
assert_eq!(x, (1.0, 2.0));
@@ -1061,14 +1003,8 @@ mod tests {
assert_eq!(log_size.to_physical::<u32>(1.0), PhysicalSize::new(1, 2));
assert_eq!(log_size.to_physical::<u32>(2.0), PhysicalSize::new(2, 4));
assert_eq!(log_size.cast::<u32>(), LogicalSize::new(1, 2));
assert_eq!(
log_size,
LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0)
);
assert_eq!(
log_size,
LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0)
);
assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(1.0, 2.0), 1.0));
assert_eq!(log_size, LogicalSize::from_physical(PhysicalSize::new(2.0, 4.0), 2.0));
assert_eq!(LogicalSize::from((2.0, 2.0)), LogicalSize::new(2.0, 2.0));
assert_eq!(LogicalSize::from([2.0, 3.0]), LogicalSize::new(2.0, 3.0));
@@ -1099,10 +1035,7 @@ mod tests {
#[test]
fn test_size() {
assert_eq!(
Size::new(PhysicalSize::new(1, 2)),
Size::Physical(PhysicalSize::new(1, 2))
);
assert_eq!(Size::new(PhysicalSize::new(1, 2)), Size::Physical(PhysicalSize::new(1, 2)));
assert_eq!(
Size::new(LogicalSize::new(1.0, 2.0)),
Size::Logical(LogicalSize::new(1.0, 2.0))

View File

@@ -1,7 +1,4 @@
#[cfg(all(
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[cfg(all(feature = "rwh_06", any(x11_platform, macos_platform, windows_platform)))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
@@ -46,25 +43,22 @@ fn main() -> Result<(), impl std::error::Error> {
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
}
},
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
}
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
// by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
// so we also can see this log when we move the cursor around (200, 200) in parent window.
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
}
},
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
@@ -72,12 +66,12 @@ fn main() -> Result<(), impl std::error::Error> {
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
}
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
},
_ => (),
},
_ => (),
@@ -85,10 +79,10 @@ fn main() -> Result<(), impl std::error::Error> {
})
}
#[cfg(all(
feature = "rwh_06",
not(any(x11_platform, macos_platform, windows_platform))
))]
#[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]
fn main() {
panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled.");
panic!(
"This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \
enabled."
);
}

View File

@@ -4,6 +4,7 @@ use std::thread;
#[cfg(not(web_platform))]
use std::time;
use ::tracing::{info, warn};
#[cfg(web_platform)]
use web_time as time;
@@ -15,6 +16,8 @@ use winit::window::{Window, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
@@ -28,13 +31,16 @@ enum Mode {
}
fn main() -> Result<(), impl std::error::Error> {
tracing_subscriber::fmt::init();
#[cfg(web_platform)]
console_error_panic_hook::set_once();
println!("Press '1' to switch to Wait mode.");
println!("Press '2' to switch to WaitUntil mode.");
println!("Press '3' to switch to Poll mode.");
println!("Press 'R' to toggle request_redraw() calls.");
println!("Press 'Esc' to close the window.");
tracing::init();
info!("Press '1' to switch to Wait mode.");
info!("Press '2' to switch to WaitUntil mode.");
info!("Press '3' to switch to Poll mode.");
info!("Press 'R' to toggle request_redraw() calls.");
info!("Press 'Esc' to close the window.");
let event_loop = EventLoop::new().unwrap();
@@ -53,7 +59,7 @@ struct ControlFlowDemo {
impl ApplicationHandler for ControlFlowDemo {
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
println!("new_events: {cause:?}");
info!("new_events: {cause:?}");
self.wait_cancelled = match cause {
StartCause::WaitCancelled { .. } => self.mode == Mode::WaitUntil,
@@ -74,49 +80,44 @@ impl ApplicationHandler for ControlFlowDemo {
_window_id: WindowId,
event: WindowEvent,
) {
println!("{event:?}");
info!("{event:?}");
match event {
WindowEvent::CloseRequested => {
self.close_requested = true;
}
},
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. },
..
} => match key.as_ref() {
// WARNING: Consider using `key_without_modifiers()` if available on your platform.
// See the `key_binding` example
Key::Character("1") => {
self.mode = Mode::Wait;
println!("\nmode: {:?}\n", self.mode);
}
warn!("mode: {:?}", self.mode);
},
Key::Character("2") => {
self.mode = Mode::WaitUntil;
println!("\nmode: {:?}\n", self.mode);
}
warn!("mode: {:?}", self.mode);
},
Key::Character("3") => {
self.mode = Mode::Poll;
println!("\nmode: {:?}\n", self.mode);
}
warn!("mode: {:?}", self.mode);
},
Key::Character("r") => {
self.request_redraw = !self.request_redraw;
println!("\nrequest_redraw: {}\n", self.request_redraw);
}
warn!("request_redraw: {}", self.request_redraw);
},
Key::Named(NamedKey::Escape) => {
self.close_requested = true;
}
},
_ => (),
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window);
}
},
_ => (),
}
}
@@ -133,11 +134,11 @@ impl ApplicationHandler for ControlFlowDemo {
event_loop
.set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME));
}
}
},
Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME);
event_loop.set_control_flow(ControlFlow::Poll);
}
},
};
if self.close_requested {

View File

@@ -1,15 +1,11 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
android_platform,
))]
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, android_platform,))]
fn main() -> std::process::ExitCode {
use std::{process::ExitCode, thread::sleep, time::Duration};
use std::process::ExitCode;
use std::thread::sleep;
use std::time::Duration;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
@@ -49,7 +45,7 @@ fn main() -> std::process::ExitCode {
WindowEvent::RedrawRequested => {
fill::fill_window(window);
window.request_redraw();
}
},
_ => (),
}
}

View File

@@ -60,13 +60,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match event {
WindowEvent::CloseRequested => {
println!("--------------------------------------------------------- Window {} CloseRequested", self.idx);
println!(
"--------------------------------------------------------- Window {} \
CloseRequested",
self.idx
);
fill::cleanup_window(window);
self.window = None;
}
},
WindowEvent::RedrawRequested => {
fill::fill_window(window);
}
},
_ => (),
}
}
@@ -76,10 +80,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_loop = EventLoop::new().unwrap();
let mut app = App {
idx: 1,
..Default::default()
};
let mut app = App { idx: 1, ..Default::default() };
event_loop.run_app_on_demand(&mut app)?;
println!("--------------------------------------------------------- Finished first loop");

View File

@@ -15,12 +15,12 @@ pub use platform::fill_window;
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::window::Window;
use winit::window::WindowId;
use winit::window::{Window, WindowId};
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
@@ -34,24 +34,32 @@ mod platform {
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: Context,
context: RefCell<Context<&'static Window>>,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface>,
surfaces: HashMap<WindowId, Surface<&'static Window, &'static Window>>,
}
impl GraphicsContext {
fn new(w: &Window) -> Self {
Self {
context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"),
context: RefCell::new(
Context::new(unsafe { mem::transmute::<&'_ Window, &'static Window>(w) })
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
}
fn create_surface(&mut self, window: &Window) -> &mut Surface {
fn create_surface(
&mut self,
window: &Window,
) -> &mut Surface<&'static Window, &'static Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, window) }
.expect("Failed to create a softbuffer surface")
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ Window, &'static Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
@@ -71,24 +79,17 @@ mod platform {
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc
.get_or_insert_with(|| GraphicsContext::new(window))
.create_surface(window);
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818;
const DARK_GRAY: u32 = 0xff181818;
surface
.resize(width, height)
.expect("Failed to resize the softbuffer surface");
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY);
buffer
.present()
.expect("Failed to present the softbuffer buffer");
buffer.present().expect("Failed to present the softbuffer buffer");
})
}

25
examples/util/tracing.rs Normal file
View File

@@ -0,0 +1,25 @@
#[cfg(not(web_platform))]
pub fn init() {
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).from_env_lossy(),
)
.init();
}
#[cfg(web_platform)]
pub fn init() {
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.without_time()
.with_writer(tracing_web::MakeWebConsoleWriter::new()),
)
.init();
}

View File

@@ -2,29 +2,28 @@
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::fmt::Debug;
#[cfg(not(any(android_platform, ios_platform)))]
use std::num::NonZeroU32;
use std::path::Path;
use std::sync::Arc;
use std::{fmt, mem};
use ::tracing::{error, info};
use cursor_icon::CursorIcon;
#[cfg(not(any(android_platform, ios_platform)))]
use rwh_05::HasRawDisplayHandle;
use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(any(android_platform, ios_platform)))]
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, Ime, WindowEvent};
use winit::event::{MouseButton, MouseScrollDelta};
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme,
Theme, Window, WindowId,
};
use winit::window::{Window, WindowId};
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
@@ -32,11 +31,21 @@ use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMac
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
#[path = "util/tracing.rs"]
mod tracing;
/// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.;
fn main() -> Result<(), Box<dyn Error>> {
#[cfg(web_platform)]
console_error_panic_hook::set_once();
tracing::init();
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let _event_loop_proxy = event_loop.create_proxy();
@@ -45,7 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
println!("Starting to send user event every second");
info!("Starting to send user event every second");
loop {
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
std::thread::sleep(std::time::Duration::from_secs(1));
@@ -74,24 +83,30 @@ struct Application {
///
/// With OpenGL it could be EGLDisplay.
#[cfg(not(any(android_platform, ios_platform)))]
context: Option<Context>,
context: Option<Context<DisplayHandle<'static>>>,
}
impl Application {
fn new<T>(event_loop: &EventLoop<T>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
let context = Some(unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() });
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
event_loop.display_handle().unwrap(),
)
})
.unwrap(),
);
// You'll have to choose an icon size at your own discretion. On X11, the desired size varies
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px,
// since it seems to work well enough in most cases. Be careful about going too high, or
// you'll be bitten by the low-quality downscaling built into the WM.
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/data/icon.png");
// You'll have to choose an icon size at your own discretion. On X11, the desired size
// varies by WM, and on Windows, you still have to account for screen scaling. Here
// we use 32px, since it seems to work well enough in most cases. Be careful about
// going too high, or you'll be bitten by the low-quality downscaling built into the
// WM.
let icon = load_icon(include_bytes!("data/icon.png"));
let icon = load_icon(Path::new(path));
println!("Loading cursor assets");
info!("Loading cursor assets");
let custom_cursors = vec![
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross.png"))),
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
@@ -123,15 +138,43 @@ impl Application {
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
println!("Using token {:?} to activate a window", token);
info!("Using token {:?} to activate a window", token);
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);
}
#[cfg(web_platform)]
{
use winit::platform::web::WindowAttributesExtWebSys;
window_attributes = window_attributes.with_append(true);
}
let window = event_loop.create_window(window_attributes)?;
#[cfg(ios_platform)]
@@ -140,11 +183,12 @@ impl Application {
window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true);
window.recognize_pan_gesture(true, 2, 2);
}
let window_state = WindowState::new(self, window)?;
let window_id = window_state.window.id();
println!("Created new window with id={window_id:?}");
info!("Created new window with id={window_id:?}");
self.windows.insert(window_id, window_state);
Ok(window_id)
}
@@ -152,23 +196,23 @@ impl Application {
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
// let cursor_position = self.cursor_position;
let window = self.windows.get_mut(&window_id).unwrap();
println!("Executing action: {action:?}");
info!("Executing action: {action:?}");
match action {
Action::CloseWindow => {
let _ = self.windows.remove(&window_id);
}
},
Action::CreateNewWindow => {
#[cfg(any(x11_platform, wayland_platform))]
if let Err(err) = window.window.request_activation_token() {
println!("Failed to get activation token: {err}");
info!("Failed to get activation token: {err}");
} else {
return;
}
if let Err(err) = self.create_window(event_loop, None) {
eprintln!("Error creating new window: {err}");
error!("Error creating new window: {err}");
}
}
},
Action::ToggleResizeIncrements => window.toggle_resize_increments(),
Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
Action::ToggleResizable => window.toggle_resizable(),
@@ -179,6 +223,12 @@ impl Application {
Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(),
Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors),
#[cfg(web_platform)]
Action::UrlCustomCursor => window.url_custom_cursor(event_loop),
#[cfg(web_platform)]
Action::AnimationCustomCursor => {
window.animation_custom_cursor(event_loop, &self.custom_cursors)
},
Action::CycleCursorGrab => window.cycle_cursor_grab(),
Action::DragWindow => window.drag_window(),
Action::DragResizeWindow => window.drag_resize_window(),
@@ -186,18 +236,25 @@ impl Application {
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::SetTheme(theme) => {
window.window.set_theme(theme);
// Get the resulting current theme to draw with
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
window.set_draw_theme(actual_theme);
},
#[cfg(macos_platform)]
Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier();
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
eprintln!("Error creating new window: {err}");
error!("Error creating new window: {err}");
}
}
},
Action::RequestResize => window.swap_dimensions(),
}
}
fn dump_monitors(&self, event_loop: &ActiveEventLoop) {
println!("Monitors information");
info!("Monitors information");
let primary_monitor = event_loop.primary_monitor();
for monitor in event_loop.available_monitors() {
let intro = if primary_monitor.as_ref() == Some(&monitor) {
@@ -207,60 +264,54 @@ impl Application {
};
if let Some(name) = monitor.name() {
println!("{intro}: {name}");
info!("{intro}: {name}");
} else {
println!("{intro}: [no name]");
info!("{intro}: [no name]");
}
let PhysicalSize { width, height } = monitor.size();
print!(" Current mode: {width}x{height}");
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
} else {
println!();
}
info!(
" Current mode: {width}x{height}{}",
if let Some(m_hz) = monitor.refresh_rate_millihertz() {
format!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000)
} else {
String::new()
}
);
let PhysicalPosition { x, y } = monitor.position();
println!(" Position: {x},{y}");
info!(" Position: {x},{y}");
println!(" Scale factor: {}", monitor.scale_factor());
info!(" Scale factor: {}", monitor.scale_factor());
println!(" Available modes (width x height x bit-depth):");
info!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth();
let m_hz = mode.refresh_rate_millihertz();
println!(
" {width}x{height}x{bits} @ {}.{} Hz",
m_hz / 1000,
m_hz % 1000
);
info!(" {width}x{height}x{bits} @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
}
}
}
/// Process the key binding.
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
KEY_BINDINGS.iter().find_map(|binding| {
binding
.is_triggered_by(&key, mods)
.then_some(binding.action)
})
KEY_BINDINGS
.iter()
.find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action))
}
/// Process mouse binding.
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
MOUSE_BINDINGS.iter().find_map(|binding| {
binding
.is_triggered_by(&button, mods)
.then_some(binding.action)
})
MOUSE_BINDINGS
.iter()
.find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action))
}
fn print_help(&self) {
println!("Keyboard bindings:");
info!("Keyboard bindings:");
for binding in KEY_BINDINGS {
println!(
info!(
"{}{:<10} - {} ({})",
modifiers_to_string(binding.mods),
binding.trigger,
@@ -268,9 +319,9 @@ impl Application {
binding.action.help(),
);
}
println!("Mouse bindings:");
info!("Mouse bindings:");
for binding in MOUSE_BINDINGS {
println!(
info!(
"{}{:<10} - {} ({})",
modifiers_to_string(binding.mods),
mouse_button_to_string(binding.trigger),
@@ -283,7 +334,7 @@ impl Application {
impl ApplicationHandler<UserEvent> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
println!("User event: {event:?}");
info!("User event: {event:?}");
}
fn window_event(
@@ -300,50 +351,46 @@ impl ApplicationHandler<UserEvent> for Application {
match event {
WindowEvent::Resized(size) => {
window.resize(size);
}
},
WindowEvent::Focused(focused) => {
if focused {
println!("Window={window_id:?} fosused");
info!("Window={window_id:?} focused");
} else {
println!("Window={window_id:?} unfosused");
}
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
println!("Window={window_id:?} changed scale to {scale_factor}");
}
WindowEvent::ThemeChanged(theme) => {
println!("Theme changed to {theme:?}");
window.set_theme(theme);
}
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
eprintln!("Error drawing window: {err}");
}
}
WindowEvent::Occluded(occluded) => {
window.set_occluded(occluded);
}
WindowEvent::CloseRequested => {
println!("Closing Window={window_id:?}");
self.windows.remove(&window_id);
}
WindowEvent::ModifiersChanged(modifiers) => {
window.modifiers = modifiers.state();
println!("Modifiers changed to {:?}", window.modifiers);
}
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::LineDelta(x, y) => {
println!("Mouse wheel Line Delta: ({x},{y})");
}
MouseScrollDelta::PixelDelta(px) => {
println!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
info!("Window={window_id:?} unfocused");
}
},
WindowEvent::KeyboardInput {
event,
is_synthetic: false,
..
} => {
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
info!("Window={window_id:?} changed scale to {scale_factor}");
},
WindowEvent::ThemeChanged(theme) => {
info!("Theme changed to {theme:?}");
window.set_draw_theme(theme);
},
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
error!("Error drawing window: {err}");
}
},
WindowEvent::Occluded(occluded) => {
window.set_occluded(occluded);
},
WindowEvent::CloseRequested => {
info!("Closing Window={window_id:?}");
self.windows.remove(&window_id);
},
WindowEvent::ModifiersChanged(modifiers) => {
window.modifiers = modifiers.state();
info!("Modifiers changed to {:?}", window.modifiers);
},
WindowEvent::MouseWheel { delta, .. } => match delta {
MouseScrollDelta::LineDelta(x, y) => {
info!("Mouse wheel Line Delta: ({x},{y})");
},
MouseScrollDelta::PixelDelta(px) => {
info!("Mouse wheel Pixel Delta: ({},{})", px.x, px.y);
},
},
WindowEvent::KeyboardInput { event, is_synthetic: false, .. } => {
let mods = window.modifiers;
// Dispatch actions only on press.
@@ -358,65 +405,68 @@ impl ApplicationHandler<UserEvent> for Application {
self.handle_action(event_loop, window_id, action);
}
}
}
},
WindowEvent::MouseInput { 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, &mods)).flatten()
{
self.handle_action(event_loop, window_id, action);
}
}
},
WindowEvent::CursorLeft { .. } => {
println!("Cursor left Window={window_id:?}");
info!("Cursor left Window={window_id:?}");
window.cursor_left();
}
},
WindowEvent::CursorMoved { position, .. } => {
println!("Moved cursor to {position:?}");
info!("Moved cursor to {position:?}");
window.cursor_moved(position);
}
},
WindowEvent::ActivationTokenDone { token: _token, .. } => {
#[cfg(any(x11_platform, wayland_platform))]
{
startup_notify::set_activation_token_env(_token);
if let Err(err) = self.create_window(event_loop, None) {
eprintln!("Error creating new window: {err}");
error!("Error creating new window: {err}");
}
}
}
},
WindowEvent::Ime(event) => match event {
Ime::Enabled => println!("IME enabled for Window={window_id:?}"),
Ime::Enabled => info!("IME enabled for Window={window_id:?}"),
Ime::Preedit(text, caret_pos) => {
println!("Preedit: {}, with caret at {:?}", text, caret_pos);
}
info!("Preedit: {}, with caret at {:?}", text, caret_pos);
},
Ime::Commit(text) => {
println!("Committed: {}", text);
}
Ime::Disabled => println!("IME disabled for Window={window_id:?}"),
info!("Committed: {}", text);
},
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
},
WindowEvent::PinchGesture { delta, .. } => {
window.zoom += delta;
let zoom = window.zoom;
if delta > 0.0 {
println!("Zoomed in {delta:.5} (now: {zoom:.5})");
info!("Zoomed in {delta:.5} (now: {zoom:.5})");
} else {
println!("Zoomed out {delta:.5} (now: {zoom:.5})");
info!("Zoomed out {delta:.5} (now: {zoom:.5})");
}
}
},
WindowEvent::RotationGesture { delta, .. } => {
window.rotated += delta;
let rotated = window.rotated;
if delta > 0.0 {
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
info!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
} else {
println!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
info!("Rotated clockwise {delta:.5} (now: {rotated:.5})");
}
}
},
WindowEvent::PanGesture { delta, phase, .. } => {
window.panned.x += delta.x;
window.panned.y += delta.y;
info!("Panned ({delta:?})) (now: {:?}), {phase:?}", window.panned);
},
WindowEvent::DoubleTapGesture { .. } => {
println!("Smart zoom");
}
info!("Smart zoom");
},
WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. }
@@ -436,23 +486,22 @@ impl ApplicationHandler<UserEvent> for Application {
device_id: DeviceId,
event: DeviceEvent,
) {
println!("Device {device_id:?} event: {event:?}");
info!("Device {device_id:?} event: {event:?}");
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
println!("Resumed the event loop");
info!("Resumed the event loop");
self.dump_monitors(event_loop);
// Create initial window.
self.create_window(event_loop, None)
.expect("failed to create initial window");
self.create_window(event_loop, None).expect("failed to create initial window");
self.print_help();
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() {
println!("No windows left, exiting...");
info!("No windows left, exiting...");
event_loop.exit();
}
}
@@ -472,9 +521,9 @@ struct WindowState {
///
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(any(android_platform, ios_platform)))]
surface: Surface,
surface: Surface<DisplayHandle<'static>, Arc<Window>>,
/// The actual winit Window.
window: Window,
window: Arc<Window>,
/// The window theme we're drawing with.
theme: Theme,
/// Cursor position over the window.
@@ -489,6 +538,8 @@ struct WindowState {
zoom: f64,
/// The amount of rotation of the window.
rotated: f32,
/// The amount of pan of the window.
panned: PhysicalPosition<f32>,
#[cfg(macos_platform)]
option_as_alt: OptionAsAlt,
@@ -501,13 +552,15 @@ struct WindowState {
impl WindowState {
fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
let window = Arc::new(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)))]
let surface = unsafe { Surface::new(app.context.as_ref().unwrap(), &window)? };
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}");
info!("Theme: {theme:?}");
let named_idx = 0;
window.set_cursor(CURSORS[named_idx]);
@@ -532,6 +585,7 @@ impl WindowState {
modifiers: Default::default(),
occluded: Default::default(),
rotated: Default::default(),
panned: Default::default(),
zoom: Default::default(),
};
@@ -543,8 +597,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, PhysicalSize::new(20, 20));
}
}
@@ -555,8 +608,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, PhysicalSize::new(20, 20));
}
}
@@ -594,7 +646,7 @@ impl WindowState {
Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)),
};
println!("Had increments: {}", new_increments.is_none());
info!("Had increments: {}", new_increments.is_none());
self.window.set_resize_increments(new_increments);
}
@@ -616,9 +668,9 @@ impl WindowState {
CursorGrabMode::Confined => CursorGrabMode::Locked,
CursorGrabMode::Locked => CursorGrabMode::None,
};
println!("Changing cursor grab mode to {:?}", self.cursor_grab);
info!("Changing cursor grab mode to {:?}", self.cursor_grab);
if let Err(err) = self.window.set_cursor_grab(self.cursor_grab) {
eprintln!("Error setting cursor grab: {err}");
error!("Error setting cursor grab: {err}");
}
}
@@ -630,16 +682,34 @@ impl WindowState {
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None,
};
println!("Setting option as alt {:?}", self.option_as_alt);
info!("Setting option as alt {:?}", self.option_as_alt);
self.window.set_option_as_alt(self.option_as_alt);
}
/// Swap the window dimensions with `request_inner_size`.
fn swap_dimensions(&mut self) {
let old_inner_size = self.window.inner_size();
let mut inner_size = old_inner_size;
mem::swap(&mut inner_size.width, &mut inner_size.height);
info!("Requesting resize from {old_inner_size:?} to {inner_size:?}");
if let Some(new_inner_size) = self.window.request_inner_size(inner_size) {
if old_inner_size == new_inner_size {
info!("Inner size change got ignored");
} else {
self.resize(new_inner_size);
}
} else {
info!("Request inner size is asynchronous");
}
}
/// Pick the next cursor.
fn next_cursor(&mut self) {
self.named_idx = (self.named_idx + 1) % CURSORS.len();
println!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
self.window
.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
info!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
self.window.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
}
/// Pick the next custom cursor.
@@ -649,24 +719,52 @@ impl WindowState {
self.window.set_cursor(cursor);
}
/// Custom cursor from an URL.
#[cfg(web_platform)]
fn url_custom_cursor(&mut self, event_loop: &ActiveEventLoop) {
let cursor = event_loop.create_custom_cursor(url_custom_cursor());
self.window.set_cursor(cursor);
}
/// Custom cursor from a URL.
#[cfg(web_platform)]
fn animation_custom_cursor(
&mut self,
event_loop: &ActiveEventLoop,
custom_cursors: &[CustomCursor],
) {
use std::time::Duration;
use winit::platform::web::CustomCursorExtWebSys;
let cursors = vec![
custom_cursors[0].clone(),
custom_cursors[1].clone(),
event_loop.create_custom_cursor(url_custom_cursor()),
];
let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = event_loop.create_custom_cursor(cursor);
self.window.set_cursor(cursor);
}
/// Resize the window to the new size.
fn resize(&mut self, _size: PhysicalSize<u32>) {
fn resize(&mut self, size: PhysicalSize<u32>) {
info!("Resized to {size:?}");
#[cfg(not(any(android_platform, ios_platform)))]
{
let (width, height) =
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) {
(Some(width), Some(height)) => (width, height),
_ => return,
};
self.surface
.resize(width, height)
.expect("failed to resize inner buffer");
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
(Some(width), Some(height)) => (width, height),
_ => return,
};
self.surface.resize(width, height).expect("failed to resize inner buffer");
}
self.window.request_redraw();
}
/// Change the theme.
fn set_theme(&mut self, theme: Theme) {
/// Change the theme that things are drawn in.
fn set_draw_theme(&mut self, theme: Theme) {
self.theme = theme;
self.window.request_redraw();
}
@@ -681,9 +779,9 @@ impl WindowState {
/// Drag the window.
fn drag_window(&self) {
if let Err(err) = self.window.drag_window() {
println!("Error starting window drag: {err}");
info!("Error starting window drag: {err}");
} else {
println!("Dragging window Window={:?}", self.window.id());
info!("Dragging window Window={:?}", self.window.id());
}
}
@@ -692,9 +790,9 @@ impl WindowState {
let position = match self.cursor_position {
Some(position) => position,
None => {
println!("Drag-resize requires cursor to be inside the window");
info!("Drag-resize requires cursor to be inside the window");
return;
}
},
};
let win_size = self.window.inner_size();
@@ -731,9 +829,9 @@ impl WindowState {
};
if let Err(err) = self.window.drag_resize_window(direction) {
println!("Error starting window drag-resize: {err}");
info!("Error starting window drag-resize: {err}");
} else {
println!("Drag-resizing window Window={:?}", self.window.id());
info!("Drag-resizing window Window={:?}", self.window.id());
}
}
@@ -749,12 +847,12 @@ impl WindowState {
#[cfg(not(any(android_platform, ios_platform)))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded {
println!("Skipping drawing occluded window={:?}", self.window.id());
info!("Skipping drawing occluded window={:?}", self.window.id());
return Ok(());
}
const WHITE: u32 = 0xFFFFFFFF;
const DARK_GRAY: u32 = 0xFF181818;
const WHITE: u32 = 0xffffffff;
const DARK_GRAY: u32 = 0xff181818;
let color = match self.theme {
Theme::Light => WHITE,
@@ -770,7 +868,7 @@ impl WindowState {
#[cfg(any(android_platform, ios_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
println!("Drawing but without rendering...");
info!("Drawing but without rendering...");
Ok(())
}
}
@@ -783,11 +881,7 @@ struct Binding<T: Eq> {
impl<T: Eq> Binding<T> {
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
Self {
trigger,
mods,
action,
}
Self { trigger, mods, action }
}
fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
@@ -809,6 +903,10 @@ enum Action {
Minimize,
NextCursor,
NextCustomCursor,
#[cfg(web_platform)]
UrlCustomCursor,
#[cfg(web_platform)]
AnimationCustomCursor,
CycleCursorGrab,
PrintHelp,
DragWindow,
@@ -816,8 +914,10 @@ enum Action {
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
}
impl Action {
@@ -835,6 +935,10 @@ impl Action {
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
Action::NextCursor => "Advance the cursor to the next value",
Action::NextCustomCursor => "Advance custom cursor to the next value",
#[cfg(web_platform)]
Action::UrlCustomCursor => "Custom cursor from an URL",
#[cfg(web_platform)]
Action::AnimationCustomCursor => "Custom cursor from an animation",
Action::CycleCursorGrab => "Cycle through cursor grab mode",
Action::PrintHelp => "Print help",
Action::DragWindow => "Start window drag",
@@ -842,8 +946,12 @@ impl Action {
Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => "Cycle option as alt mode",
Action::SetTheme(None) => "Change to the system theme",
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
#[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
}
}
}
@@ -862,11 +970,24 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
}
fn load_icon(path: &Path) -> Icon {
#[cfg(web_platform)]
fn url_custom_cursor() -> CustomCursorSource {
use std::sync::atomic::{AtomicU64, Ordering};
use winit::platform::web::CustomCursorExtWebSys;
static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
CustomCursor::from_url(
format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)),
64,
64,
)
}
fn load_icon(bytes: &[u8]) -> Icon {
let (icon_rgba, icon_width, icon_height) = {
let image = image::open(path)
.expect("Failed to open icon path")
.into_rgba8();
let image = image::load_from_memory(bytes).unwrap().into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
@@ -950,6 +1071,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
// M.
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize),
@@ -958,7 +1080,23 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
// C.
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
#[cfg(web_platform)]
Binding::new(
"C",
ModifiersState::CONTROL.union(ModifiersState::SHIFT),
Action::UrlCustomCursor,
),
#[cfg(web_platform)]
Binding::new(
"C",
ModifiersState::ALT.union(ModifiersState::SHIFT),
Action::AnimationCustomCursor,
),
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
// K.
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)]
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)]
@@ -966,19 +1104,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
];
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
Binding::new(
MouseButton::Left,
ModifiersState::ALT,
Action::DragResizeWindow,
),
Binding::new(
MouseButton::Left,
ModifiersState::CONTROL,
Action::DragWindow,
),
Binding::new(
MouseButton::Right,
ModifiersState::CONTROL,
Action::ShowWindowMenu,
),
Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow),
Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow),
Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu),
];

View File

@@ -39,7 +39,7 @@ fn main() -> Result<(), Box<dyn Error>> {
WindowEvent::RedrawRequested => {
window.pre_present_notify();
fill::fill_window(window);
}
},
_ => (),
}
}
@@ -58,10 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?;
let mut app = XEmbedDemo {
parent_window_id,
window: None,
};
let mut app = XEmbedDemo { parent_window_id, window: None };
event_loop.run_app(&mut app).map_err(Into::into)
}

View File

@@ -1,11 +0,0 @@
[package]
name = "run-wasm"
version = "0.1.0"
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
publish = false
[dependencies]
cargo-run-wasm = "0.2.0"

View File

@@ -1,3 +0,0 @@
fn main() {
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
}

View File

@@ -1,3 +1,19 @@
force_explicit_abi=true
use_field_init_shorthand=true
# merge_imports=true
format_code_in_doc_comments = true
match_block_trailing_comma = true
condense_wildcard_suffixes = true
use_field_init_shorthand = true
normalize_doc_attributes = true
overflow_delimited_expr = true
imports_granularity = "Module"
use_small_heuristics = "Max"
format_macro_matchers = true
error_on_unformatted = true
format_macro_bodies = true
hex_literal_case = "Lower"
normalize_comments = true
# Some macros break with this.
reorder_impl_items = false
newline_style = "Unix"
format_strings = true
wrap_comments = true
comment_width = 100

View File

@@ -11,7 +11,7 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// This is a useful place to put code that should be done before you start processing
/// events, such as updating frame timing information for benchmarking or checking the
/// [`StartCause`] to see if a timer set by
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
/// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed.
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
let _ = (event_loop, cause);
}
@@ -20,8 +20,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
///
/// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a
/// formal suspend/resume lifecycle. For systems without a formal suspend/resume lifecycle
/// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init]
/// event.
/// the `Resumed` event is always emitted after the
/// [`NewEvents(StartCause::Init)`][StartCause::Init] event.
///
/// # Portability
///
@@ -33,15 +33,16 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
/// driven by multiple platform-specific events, and that there may be subtle differences across
/// platforms with how these internal events are delivered, it's recommended that applications
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events.
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed`
/// events.
///
/// Also see [`Suspended`] notes.
///
/// ## Android
///
/// On Android, the `Resumed` event is sent 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.
/// expected to closely correlate with the [`onResume`] lifecycle event but there may
/// technically be a discrepancy.
///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
///
@@ -109,7 +110,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when the event loop is about to block and wait for new events.
///
/// Most applications shouldn't need to hook into this event since there is no real relationship
/// between how often the event loop needs to wake up and the dispatching of any specific events.
/// between how often the event loop needs to wake up and the dispatching of any specific
/// events.
///
/// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events.
@@ -133,7 +135,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
/// driven by multiple platform-specific events, and that there may be subtle differences across
/// platforms with how these internal events are delivered, it's recommended that applications
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events.
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`]
/// events.
///
/// Also see [`Resumed`] notes.
///
@@ -150,7 +153,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// 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`].
/// the event callback completes, which may be re-created when the application is next
/// [`Resumed`].
///
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
@@ -186,7 +190,7 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this method is called, it is guaranteed that the event loop
/// will exist right after.
/// will exit right after.
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
}
@@ -197,17 +201,17 @@ pub trait ApplicationHandler<T: 'static = ()> {
///
/// ### Android
///
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application
/// must [release memory] or risk being killed.
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The
/// application must [release memory] or risk being killed.
///
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
/// [release memory]: https://developer.android.com/topic/performance/memory#release
///
/// ### iOS
///
/// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`]
/// callback. The application must free as much memory as possible or risk being terminated, see
/// [how to respond to memory warnings].
/// On iOS, the `MemoryWarning` event is emitted in response to an
/// [`applicationDidReceiveMemoryWarning`] callback. The application must free as much
/// memory as possible or risk being terminated, see [how to respond to memory warnings].
///
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni
/// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings
@@ -219,3 +223,117 @@ pub trait ApplicationHandler<T: 'static = ()> {
let _ = event_loop;
}
}
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
}
#[inline]
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
(**self).resumed(event_loop);
}
#[inline]
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, event);
}
#[inline]
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
}
#[inline]
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
(**self).about_to_wait(event_loop);
}
#[inline]
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
(**self).suspended(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
}
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
}
#[inline]
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
(**self).resumed(event_loop);
}
#[inline]
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
}
#[inline]
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, event);
}
#[inline]
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
}
#[inline]
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
(**self).about_to_wait(event_loop);
}
#[inline]
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
(**self).suspended(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
}

View File

@@ -2,11 +2,13 @@
//!
//! All notable changes to this project will be documented in this module,
//! along with migration instructions for larger changes.
//!
// Put the current entry at the top of this page, for discoverability.
// See `.cargo/config.toml` for details about `unreleased_changelogs`.
#![cfg_attr(unreleased_changelogs, doc = include_str!("unreleased.md"))]
#![cfg_attr(not(unreleased_changelogs), doc = include_str!("v0.29.md"))]
#![cfg_attr(not(unreleased_changelogs), doc = include_str!("v0.30.md"))]
#[doc = include_str!("v0.30.md")]
pub mod v0_30 {}
#[doc = include_str!("v0.29.md")]
pub mod v0_29 {}

View File

@@ -1,42 +1,41 @@
## Unreleased
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Deprecate `EventLoop::run` in favor of `EventLoop::run_app`.
- Deprecate `EventLoopExtRunOnDemand::run_on_demand` in favor of `EventLoop::run_app_on_demand`.
- Deprecate `EventLoopExtPumpEvents::pump_events` in favor of `EventLoopExtPumpEvents::pump_app_events`.
- Add `ApplicationHandler<T>` trait which mimics `Event<T>`.
- Move `dpi` types to its own crate, and re-export it from the root crate.
- Implement `Sync` for `EventLoopProxy<T: Send>`.
- **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).
- **Breaking:** Rename `EventLoopWindowTarget` to `ActiveEventLoop`.
- **Breaking:** Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`.
- **Breaking**: Remove `WindowBuilder` in favor of `WindowAttributes`.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
- On macOS, add services menu.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
- On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
- **Breaking:** No longer export `platform::x11::XNotSupported`.
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
- Add `Window::default_attributes` to get default `WindowAttributes`.
- `log` has been replaced with `tracing`. The old behavior can be emulated by setting the `log` feature on the `tracing` crate.
The sections should follow the order `Added`, `Changed`, `Deprecated`,
`Removed`, and `Fixed`.
Platform specific changed should be added to the end of the section and grouped
by platform name. Common API additions should have `, implemented` at the end
for platforms where the API was initially implemented. See the following example
on how to add them:
```md
### Added
- Add `Window::turbo()`, implemented on X11, Wayland, and Web.
- On X11, add `Window::some_rare_api`.
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
- On Windows, add `Window::some_rare_api`.
```
When the change requires non-trivial amount of work for users to comply
with it, the migration guide should be added below the entry, like:
```md
- Deprecate `Window` creation outside of `EventLoop::run`
This was done to simply migration in the future. Consider the
following code:
// Code snippet.
To migrate it we should do X, Y, and then Z, for example:
// Code snippet.
```
The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased

View File

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

331
src/changelog/v0.30.md Normal file
View File

@@ -0,0 +1,331 @@
## 0.30.7
### Fixed
- On X11, fixed KeyboardInput delivered twice when IME enabled.
## 0.30.6
### Added
- 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 X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- 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`.
### Fixed
- 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 macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4
### Changed
- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`.
### Fixed
- On Wayland, avoid crashing when compositor is misbehaving.
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
to not be de-duplicated.
- Account for different browser engine implementations of pointer movement coordinate space.
## 0.30.3
### Added
- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting
control flow strategies before starting the event loop.
- On Web, add `WaitUntilStrategy`, which allows to set different strategies for
`ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is
used, with a fallback to `setTimeout()` with a trick to circumvent throttling
to 4ms. But an option to use a Web worker to schedule the timer is available
as well, which commonly prevents any throttling when the window is not focused.
### Changed
- On macOS, set the window theme on the `NSWindow` instead of application-wide.
### Fixed
- On X11, build on arm platforms.
- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window.
## 0.30.2
### Fixed
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
when not called from inside the event loop. Now queues a microtask instead.
- On Web, stop overwriting default cursor with `CursorIcon::Default`.
- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`.
- On macOS, fix not working opacity for entire window.
## 0.30.1
### Added
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.
- On macOS, fix the range in `Ime::Preedit`.
- On macOS, use the system's internal mechanisms for queuing events.
- On macOS, handle events directly instead of queuing when possible.
## 0.30.0
### Added
- Add `OwnedDisplayHandle` type for allowing safe display handle usage outside of
trivial cases.
- Add `ApplicationHandler<T>` trait which mimics `Event<T>`.
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a
`CursorIcon` or `CustomCursor`.
- Add `Sync` implementation for `EventLoopProxy<T: Send>`.
- Add `Window::default_attributes` to get default `WindowAttributes`.
- Add `EventLoop::builder` to get `EventLoopBuilder` without export.
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated
cursors from other `CustomCursor`s.
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources.
- Add `ActiveEventLoop::create_window` and `EventLoop::create_window`.
- Add `CustomCursor` which could be set via `Window::set_cursor`, implemented on
Windows, macOS, X11, Wayland, and Web.
- On Web, add to toggle calling `Event.preventDefault()` on `Window`.
- On iOS, add `PinchGesture`, `DoubleTapGesture`, `PanGesture` and `RotationGesture`.
- on iOS, use `UIGestureRecognizerDelegate` for fine grained control of gesture recognizers.
- On macOS, add services menu.
- On Windows, add `with_title_text_color`, and `with_corner_preference` on
`WindowAttributesExtWindows`.
- On Windows, implement resize increments.
- On Windows, add `AnyThread` API to access window handle off the main thread.
### Changed
- Bump MSRV from `1.65` to `1.70`.
- On Wayland, bump `sctk-adwaita` to `0.9.0`, which changed system library
crates. This change is a **cascading breaking change**, you must do breaking
change as well, even if you don't expose winit.
- Rename `TouchpadMagnify` to `PinchGesture`.
- Rename `SmartMagnify` to `DoubleTapGesture`.
- Rename `TouchpadRotate` to `RotationGesture`.
- Rename `EventLoopWindowTarget` to `ActiveEventLoop`.
- Rename `platform::x11::XWindowType` to `platform::x11::WindowType`.
- Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold
static data.
- Make `Debug` formatting of `WindowId` more concise.
- Move `dpi` types to its own crate, and re-export it from the root crate.
- Replace `log` with `tracing`, use `log` feature on `tracing` to restore old
behavior.
- `EventLoop::with_user_event` now returns `EventLoopBuilder`.
- On Web, return `HandleError::Unavailable` when a window handle is not available.
- On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
- On Web, remove queuing fullscreen request in absence of transient activation.
- On iOS, return `HandleError::Unavailable` when a window handle is not available.
- On macOS, return `HandleError::Unavailable` when a window handle is not available.
- On Windows, remove `WS_CAPTION`, `WS_BORDER`, and `WS_EX_WINDOWEDGE` styles
for child windows without decorations.
- On Android, bump `ndk` to `0.9.0` and `android-activity` to `0.6.0`,
and remove unused direct dependency on `ndk-sys`.
### Deprecated
- Deprecate `EventLoop::run`, use `EventLoop::run_app`.
- Deprecate `EventLoopExtRunOnDemand::run_on_demand`, use `EventLoop::run_app_on_demand`.
- Deprecate `EventLoopExtPumpEvents::pump_events`, use `EventLoopExtPumpEvents::pump_app_events`.
The new `app` APIs accept a newly added `ApplicationHandler<T>` instead of
`Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code:
```rust,no_run
use winit::event::Event;
use winit::event_loop::EventLoop;
use winit::window::Window;
struct MyUserEvent;
let event_loop = EventLoop::<MyUserEvent>::with_user_event().build().unwrap();
let window = event_loop.create_window(Window::default_attributes()).unwrap();
let mut counter = 0;
let _ = event_loop.run(move |event, event_loop| {
match event {
Event::AboutToWait => {
window.request_redraw();
counter += 1;
}
Event::WindowEvent { window_id, event } => {
// Handle window event.
}
Event::UserEvent(event) => {
// Handle user event.
}
Event::DeviceEvent { device_id, event } => {
// Handle device event.
}
_ => (),
}
});
```
To migrate this code, you should move all the captured values into some
newtype `State` and implement `ApplicationHandler` for this type. Finally,
we move particular `match event` arms into methods on `ApplicationHandler`,
for example:
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};
use winit::window::{Window, WindowId};
struct MyUserEvent;
struct State {
window: Window,
counter: i32,
}
impl ApplicationHandler<MyUserEvent> for State {
fn user_event(&mut self, event_loop: &ActiveEventLoop, user_event: MyUserEvent) {
// Handle user event.
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
// Your application got resumed.
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
// Handle window event.
}
fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) {
// Handle device event.
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.window.request_redraw();
self.counter += 1;
}
}
let event_loop = EventLoop::<MyUserEvent>::with_user_event().build().unwrap();
#[allow(deprecated)]
let window = event_loop.create_window(Window::default_attributes()).unwrap();
let mut state = State { window, counter: 0 };
let _ = event_loop.run_app(&mut state);
```
Please submit your feedback after migrating in [this issue](https://github.com/rust-windowing/winit/issues/3626).
- Deprecate `Window::set_cursor_icon`, use `Window::set_cursor`.
### Removed
- Remove `Window::new`, use `ActiveEventLoop::create_window` instead.
You now have to create your windows inside the actively running event loop
(usually the `new_events(cause: StartCause::Init)` or `resumed()` events),
and can no longer do it before the application has properly launched.
This change is done to fix many long-standing issues on iOS and macOS, and
will improve things on Wayland once fully implemented.
To ease migration, we provide the deprecated `EventLoop::create_window` that
will allow you to bypass this restriction in this release.
Using the migration example from above, you can change your code as follows:
```rust,no_run
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};
use winit::window::{Window, WindowId};
#[derive(Default)]
struct State {
// Use an `Option` to allow the window to not be available until the
// application is properly running.
window: Option<Window>,
counter: i32,
}
impl ApplicationHandler for State {
// This is a common indicator that you can create a window.
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
// `unwrap` is fine, the window will always be available when
// receiving a window event.
let window = self.window.as_ref().unwrap();
// Handle window event.
}
fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) {
// Handle window event.
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
window.request_redraw();
self.counter += 1;
}
}
}
let event_loop = EventLoop::new().unwrap();
let mut state = State::default();
let _ = event_loop.run_app(&mut state);
```
- Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`.
- Remove `WindowBuilder` in favor of `WindowAttributes`.
- Remove Generic parameter `T` from `ActiveEventLoop`.
- Remove `EventLoopBuilder::with_user_event`, use `EventLoop::with_user_event`.
- Remove Redundant `EventLoopError::AlreadyRunning`.
- Remove `WindowAttributes::fullscreen` and expose as field directly.
- On X11, remove `platform::x11::XNotSupported` export.
### Fixed
- On Web, fix setting cursor icon overriding cursor visibility.
- On Windows, fix cursor not confined to center of window when grabbed and hidden.
- On macOS, fix sequence of mouse events being out of order when dragging on the trackpad.
- On Wayland, fix decoration glitch on close with some compositors.
- On Android, fix a regression introduced in #2748 to allow volume key events to be received again.
- On Windows, don't return a valid window handle outside of the GUI thread.
- On macOS, don't set the background color when initializing a window with transparency.

View File

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

View File

@@ -1,7 +1,7 @@
use core::fmt;
use std::hash::Hasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::{error::Error, hash::Hash};
use cursor_icon::CursorIcon;
@@ -12,7 +12,7 @@ pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// See [`Window::set_cursor()`](crate::window::Window::set_cursor) for more details.
/// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Cursor {
Icon(CursorIcon),
@@ -88,14 +88,9 @@ impl CustomCursor {
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
let _span = tracing::debug_span!(
"winit::Cursor::from_rgba",
width,
height,
hotspot_x,
hotspot_y
)
.entered();
let _span =
tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
.entered();
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
@@ -129,45 +124,36 @@ pub enum BadImage {
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u16,
height: u16,
width_x_height: u64,
pixel_count: u64,
},
DimensionsVsPixelCount { width: u16, height: u16, width_x_height: u64, pixel_count: u64 },
/// Produced when the hotspot is outside the image bounds
HotspotOutOfBounds {
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
},
HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
}
impl fmt::Display for BadImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadImage::TooLarge { width, height } => write!(f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is {MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
BadImage::TooLarge { width, height } => write!(
f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
{MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
),
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
BadImage::ByteCountNotDivisibleBy4 { byte_count } => write!(
f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
it impossible to interpret as 32bpp RGBA pixels.",
),
BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
),
BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
} => write!(f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds ({width:?}x{height:?}).",
BadImage::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
write!(
f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of \
pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
dimensions, the expected pixel count is {width_x_height:?}.",
)
},
BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
({width:?}x{height:?}).",
),
}
}
@@ -236,9 +222,7 @@ impl CursorImage {
}
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadImage::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
@@ -253,21 +237,10 @@ impl CursorImage {
}
if hotspot_x >= width || hotspot_y >= height {
return Err(BadImage::HotspotOutOfBounds {
width,
height,
hotspot_x,
hotspot_y,
});
return Err(BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y });
}
Ok(CursorImage {
rgba,
width,
height,
hotspot_x,
hotspot_y,
})
Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
}
}

View File

@@ -71,10 +71,7 @@ macro_rules! os_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
))
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
@@ -117,19 +114,14 @@ impl error::Error for NotSupportedError {}
impl error::Error for EventLoopError {}
#[cfg(test)]
#[allow(clippy::redundant_clone)]
mod tests {
#![allow(clippy::redundant_clone)]
use super::*;
// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
let _ = format!("{:?}, {}", NotSupportedError::new(), NotSupportedError::new().clone());
let _ = format!(
"{:?}, {}",
ExternalError::NotSupported(NotSupportedError::new()),

View File

@@ -1,7 +1,8 @@
//! The [`Event`] enum and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level documentation.
//! processed and used to modify the program state. For more details, see the root-level
//! documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
@@ -44,16 +45,14 @@ use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::ExternalError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)]
use crate::window::Window;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial,
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
platform_impl,
window::{ActivationToken, Theme, WindowId},
};
use crate::window::{ActivationToken, Theme, WindowId};
/// Describes a generic event.
///
@@ -68,18 +67,12 @@ pub enum Event<T: 'static> {
/// See [`ApplicationHandler::window_event`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
WindowEvent {
window_id: WindowId,
event: WindowEvent,
},
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
DeviceEvent { device_id: DeviceId, event: DeviceEvent },
/// See [`ApplicationHandler::user_event`] for details.
///
@@ -138,17 +131,11 @@ pub enum StartCause {
/// guaranteed to be equal to or after the requested resume time.
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
ResumeTimeReached {
start: Instant,
requested_resume: Instant,
},
ResumeTimeReached { start: Instant, requested_resume: Instant },
/// Sent if the OS has new events to send to the window, after a wait was requested. Contains
/// the moment the wait was requested and the resume time, if requested.
WaitCancelled {
start: Instant,
requested_resume: Option<Instant>,
},
WaitCancelled { start: Instant, requested_resume: Option<Instant> },
/// Sent if the event loop is being resumed after the loop's control flow was set to
/// [`ControlFlow::Poll`].
@@ -164,18 +151,11 @@ pub enum StartCause {
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
///
#[cfg_attr(
not(any(x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
#[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone {
serial: AsyncRequestSerial,
token: ActivationToken,
},
ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>),
@@ -229,10 +209,10 @@ pub enum WindowEvent {
/// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances:
///
/// * Synthetic key press events are generated for all keys pressed
/// when a window gains focus. Likewise, synthetic key release events
/// are generated for all keys pressed when a window goes out of focus.
/// ***Currently, this is only functional on X11 and Windows***
/// * Synthetic key press events are generated for all keys pressed when a window gains
/// focus. Likewise, synthetic key release events are generated for all keys pressed when
/// a window goes out of focus. ***Currently, this is only functional on X11 and
/// Windows***
///
/// Otherwise, this value is always `false`.
is_synthetic: bool,
@@ -262,9 +242,10 @@ pub enum WindowEvent {
CursorMoved {
device_id: 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) 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.
position: PhysicalPosition<f64>,
},
@@ -291,18 +272,10 @@ pub enum WindowEvent {
CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
},
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase },
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
},
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton },
/// Two-finger pinch gesture, often used for magnification.
///
@@ -320,6 +293,19 @@ pub enum WindowEvent {
phase: TouchPhase,
},
/// N-finger pan gesture
///
/// ## Platform-specific
///
/// - Only available on **iOS**.
/// - On iOS, not recognized by default. It must be enabled when needed.
PanGesture {
device_id: DeviceId,
/// Change in pixels of pan gesture from last update.
delta: PhysicalPosition<f32>,
phase: TouchPhase,
},
/// Double tap gesture.
///
/// On a Mac, smart magnification is triggered by a double tap with two fingers
@@ -351,6 +337,7 @@ pub enum WindowEvent {
/// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture {
device_id: DeviceId,
/// change in rotation in degrees
delta: f32,
phase: TouchPhase,
},
@@ -358,20 +345,12 @@ 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,
},
/// 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 },
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
/// Touch event has been received
///
@@ -393,8 +372,8 @@ 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 window is
/// resized to the value suggested by the OS, but it can be changed to any value.
/// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the
/// window is resized to the value suggested by the OS, but it can be changed to any value.
///
/// For more information about DPI in general, see the [`dpi`] crate.
ScaleFactorChanged {
@@ -410,6 +389,8 @@ pub enum WindowEvent {
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
///
/// ## Platform-specific
///
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
@@ -424,10 +405,11 @@ pub enum WindowEvent {
///
/// ### iOS
///
/// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`]
/// callback which means the application should start preparing its data. The `Occluded(true)` event is
/// emitted in response to an [`applicationDidEnterBackground`] callback which means the application
/// should free resources (according to the [iOS application lifecycle]).
/// On iOS, the `Occluded(false)` event is emitted in response to an
/// [`applicationWillEnterForeground`] callback which means the application should start
/// preparing its data. The `Occluded(true)` event is emitted in response to an
/// [`applicationDidEnterBackground`] callback which means the application should free
/// resources (according to the [iOS application lifecycle]).
///
/// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
/// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
@@ -457,34 +439,32 @@ pub enum WindowEvent {
/// Identifier of an input device.
///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
/// Whenever you receive an event arising from a particular input device, this event contains a
/// `DeviceId` which identifies its origin. Note that devices may be virtual (representing an
/// 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 DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Safety
/// # Notes
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
pub const fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
}
}
/// Represents raw hardware events that are not associated with any particular window.
///
/// 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.
/// 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.
///
/// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)]
@@ -494,7 +474,8 @@ pub enum DeviceEvent {
/// Change in physical position of a pointing device.
///
/// This represents raw, unfiltered physical motion. Not to be confused with [`WindowEvent::CursorMoved`].
/// This represents raw, unfiltered physical motion. Not to be confused with
/// [`WindowEvent::CursorMoved`].
MouseMotion {
/// (x, y) change in position in unspecified units.
///
@@ -551,11 +532,11 @@ pub struct KeyEvent {
/// ## Caveats
///
/// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
/// implements DVORAK in hardware (or firmware)
/// implements DVORAK in hardware (or firmware)
/// - Your application will likely have to handle keyboards which are missing keys that your
/// own keyboard has.
/// own keyboard has.
/// - Certain `KeyCode`s will move between a couple of different positions depending on what
/// layout the keyboard was manufactured to support.
/// layout the keyboard was manufactured to support.
///
/// **Because of these caveats, it is important that you provide users with a way to configure
/// most (if not all) keybinds in your application.**
@@ -577,8 +558,7 @@ pub struct KeyEvent {
///
/// This has two use cases:
/// - Allows querying whether the current input is a Dead key.
/// - Allows handling key-bindings on platforms which don't
/// support [`key_without_modifiers`].
/// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`].
///
/// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
/// shortcuts, **it is important that you provide users with a way to configure your
@@ -586,8 +566,8 @@ pub struct KeyEvent {
/// incompatible keyboard layout.**
///
/// ## Platform-specific
/// - **Web:** Dead keys might be reported as the real key instead
/// of `Dead` depending on the browser/OS.
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
@@ -615,13 +595,13 @@ pub struct KeyEvent {
/// Contains the location of this key on the keyboard.
///
/// Certain keys on the keyboard may appear in more than once place. For example, the "Shift" key
/// appears on the left side of the QWERTY keyboard as well as the right side. However, both keys
/// have the same symbolic value. Another example of this phenomenon is the "1" key, which appears
/// both above the "Q" key and as the "Keypad 1" key.
/// Certain keys on the keyboard may appear in more than once place. For example, the "Shift"
/// key appears on the left side of the QWERTY keyboard as well as the right side. However,
/// both keys have the same symbolic value. Another example of this phenomenon is the "1"
/// key, which appears both above the "Q" key and as the "Keypad 1" key.
///
/// This field allows the user to differentiate between keys like this that have the same symbolic
/// value but different locations on the keyboard.
/// This field allows the user to differentiate between keys like this that have the same
/// symbolic value but different locations on the keyboard.
///
/// See the [`KeyLocation`] type for more details.
///
@@ -636,8 +616,8 @@ pub struct KeyEvent {
/// Whether or not this key is a key repeat event.
///
/// On some systems, holding down a key for some period of time causes that key to be repeated
/// as though it were being pressed and released repeatedly. This field is `true` if and only if
/// this event is the result of one of those repeats.
/// as though it were being pressed and released repeatedly. This field is `true` if and only
/// if this event is the result of one of those repeats.
///
/// # Example
///
@@ -645,30 +625,31 @@ pub struct KeyEvent {
/// done by ignoring events where this property is set.
///
/// ```
/// use winit::event::{WindowEvent, KeyEvent, ElementState};
/// use winit::event::{ElementState, KeyEvent, WindowEvent};
/// use winit::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event {
/// WindowEvent::KeyboardInput {
/// event: KeyEvent {
/// physical_key: PhysicalKey::Code(KeyCode::KeyW),
/// state: ElementState::Pressed,
/// repeat: false,
/// ..
/// },
/// event:
/// KeyEvent {
/// physical_key: PhysicalKey::Code(KeyCode::KeyW),
/// state: ElementState::Pressed,
/// repeat: false,
/// ..
/// },
/// ..
/// } => {
/// // The physical key `W` was pressed, and it was not a repeat
/// }
/// _ => {} // Handle other events
/// // The physical key `W` was pressed, and it was not a repeat
/// },
/// _ => {}, // Handle other events
/// }
/// ```
pub repeat: bool,
/// Platform-specific key event information.
///
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with all
/// modifiers applied.
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// all modifiers applied.
///
/// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra,
@@ -742,10 +723,7 @@ impl Modifiers {
impl From<ModifiersState> for Modifiers {
fn from(value: ModifiersState) -> Self {
Self {
state: value,
pressed_mods: Default::default(),
}
Self { state: value, pressed_mods: Default::default() }
}
}
@@ -753,12 +731,16 @@ impl From<ModifiersState> for Modifiers {
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::KeyboardInput`].
/// However, one couldn't possibly have a key for every single unicode character that the user might want to type
/// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead.
/// Most keypresses using a latin-like keyboard layout simply generate a
/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
/// unicode character that the user might want to type
/// - so the solution operating systems employ is to allow the user to type these using _a sequence
/// of keypresses_ instead.
///
/// A prominent example of this is accents - many keyboard layouts allow you to first click the
/// "accent key", and then the character you want to apply the accent to. In this case, some
/// platforms will generate the following event sequence:
///
/// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then
/// the character you want to apply the accent to. In this case, some platforms will generate the following event sequence:
/// ```ignore
/// // Press "`" key
/// Ime::Preedit("`", Some((0, 0)))
@@ -767,11 +749,13 @@ impl From<ModifiersState> for Modifiers {
/// Ime::Commit("é")
/// ```
///
/// Additionally, certain input devices are configured to display a candidate box that allow the user to select the
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_cursor_area`].)
/// Additionally, certain input devices are configured to display a candidate box that allow the
/// user to select the desired character interactively. (To properly position this box, you must use
/// [`Window::set_ime_cursor_area`].)
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
/// following event sequence could be obtained:
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the following event
/// sequence could be obtained:
/// ```ignore
/// // Press "A" key
/// Ime::Preedit("a", Some((1, 1)))
@@ -790,8 +774,8 @@ impl From<ModifiersState> for Modifiers {
pub enum Ime {
/// Notifies when the IME was enabled.
///
/// After getting this event you could receive [`Preedit`](Self::Preedit) and
/// [`Commit`](Self::Commit) events. You should also start performing IME related requests
/// After getting this event you could receive [`Preedit`][Self::Preedit] and
/// [`Commit`][Self::Commit] events. You should also start performing IME related requests
/// like [`Window::set_ime_cursor_area`].
Enabled,
@@ -811,10 +795,10 @@ pub enum Ime {
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or
/// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending
/// preedit text.
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
/// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should
/// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear
/// pending preedit text.
Disabled,
}
@@ -865,8 +849,8 @@ pub struct Touch {
///
/// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
/// - **Android**: This will never be [None]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -913,17 +897,13 @@ impl Force {
/// consistent across devices.
pub fn normalized(&self) -> f64 {
match self {
Force::Calibrated {
force,
max_possible_force,
altitude_angle,
} => {
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::Normalized(force) => *force,
}
}
@@ -1029,6 +1009,7 @@ impl PartialEq for InnerSizeWriter {
#[cfg(test)]
mod tests {
use crate::dpi::PhysicalPosition;
use crate::event;
use std::collections::{BTreeSet, HashSet};
@@ -1036,15 +1017,17 @@ mod tests {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
let did = event::DeviceId::dummy();
#[allow(deprecated)]
{
use crate::event::{Event::*, Ime::Enabled, WindowEvent::*};
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::window::WindowId;
// Mainline events.
let wid = unsafe { WindowId::dummy() };
let wid = WindowId::dummy();
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
@@ -1053,12 +1036,7 @@ mod tests {
x(Resumed);
// Window events.
let with_window_event = |wev| {
x(WindowEvent {
window_id: wid,
event: wev,
})
};
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
with_window_event(CloseRequested);
with_window_event(Destroyed);
@@ -1069,10 +1047,7 @@ mod tests {
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(CursorMoved { device_id: did, position: (0, 0).into() });
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did });
@@ -1097,16 +1072,13 @@ mod tests {
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure {
with_window_event(PanGesture {
device_id: did,
pressure: 0.0,
stage: 0,
});
with_window_event(AxisMotion {
device_id: did,
axis: 0,
value: 0.0,
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(AxisMotion { device_id: did, axis: 0, value: 0.0 });
with_window_event(Touch(event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
@@ -1122,29 +1094,17 @@ mod tests {
{
use event::DeviceEvent::*;
let with_device_event = |dev_ev| {
x(event::Event::DeviceEvent {
device_id: did,
event: dev_ev,
})
};
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: did, event: dev_ev });
with_device_event(Added);
with_device_event(Removed);
with_device_event(MouseMotion {
delta: (0.0, 0.0).into(),
});
with_device_event(MouseMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Motion {
axis: 0,
value: 0.0,
});
with_device_event(Button {
button: 0,
state: event::ElementState::Pressed,
});
with_device_event(Motion { axis: 0, value: 0.0 });
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}
}};
}
@@ -1176,11 +1136,8 @@ 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, altitude_angle: None };
assert_eq!(force2.normalized(), 2.0);
let force3 = event::Force::Calibrated {
@@ -1199,7 +1156,7 @@ mod tests {
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
let did = crate::event::DeviceId::dummy().clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();
@@ -1219,11 +1176,8 @@ mod tests {
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, altitude_angle: None }
.clone();
}
}

View File

@@ -3,7 +3,7 @@
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`send_event`](`EventLoopProxy::send_event`) method.
//! [`send_event`][EventLoopProxy::send_event] method.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
@@ -20,8 +20,10 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
use crate::event::Event;
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
@@ -33,8 +35,8 @@ use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
/// [`Window`] created from this _can_ be sent to an other thread, and the
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
@@ -94,18 +96,19 @@ impl<T> EventLoopBuilder<T> {
///
/// ## Platform-specific
///
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY`
/// or `DISPLAY` respectively when building the event loop.
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or
/// `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android,
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
android_platform,
doc = "[`.with_android_app(app)`]: \
crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android),
not(android_platform),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
@@ -162,9 +165,9 @@ pub enum ControlFlow {
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the display's
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
/// to reduce odds of missed frames.
/// Useful for implementing efficient timers. Applications which want to render at the
/// display's native refresh rate should instead use [`Poll`] and the VSync functionality
/// of a graphics API to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
@@ -210,10 +213,7 @@ impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<T> {
EventLoopBuilder {
platform_specific: Default::default(),
_p: PhantomData,
}
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// See [`run_app`].
@@ -239,9 +239,9 @@ impl<T> EventLoop<T> {
///
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed
/// and any values not passed to this function will *not* be dropped.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
/// never executed and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use
#[cfg_attr(
@@ -262,25 +262,20 @@ impl<T> EventLoop<T> {
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop
.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
}
EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() }
}
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.event_loop.window_target().p.owned_display_handle(),
}
OwnedDisplayHandle { platform: self.event_loop.window_target().p.owned_display_handle() }
}
/// Change if or when [`DeviceEvent`]s are captured.
@@ -295,18 +290,12 @@ impl<T> EventLoop<T> {
)
.entered();
self.event_loop
.window_target()
.p
.listen_device_events(allowed);
self.event_loop.window_target().p.listen_device_events(allowed);
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop
.window_target()
.p
.set_control_flow(control_flow)
self.event_loop.window_target().p.set_control_flow(control_flow)
}
/// Create a window.
@@ -329,10 +318,7 @@ impl<T> EventLoop<T> {
/// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.event_loop
.window_target()
.p
.create_custom_cursor(custom_cursor)
self.event_loop.window_target().p.create_custom_cursor(custom_cursor)
}
}
@@ -413,10 +399,7 @@ impl ActiveEventLoop {
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
#[allow(clippy::useless_conversion)] // false positive on some platforms
self.p
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
self.p.available_monitors().into_iter().map(|inner| MonitorHandle { inner })
}
/// Returns the primary monitor of the system.
@@ -430,9 +413,7 @@ impl ActiveEventLoop {
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
self.p
.primary_monitor()
.map(|inner| MonitorHandle { inner })
self.p.primary_monitor().map(|inner| MonitorHandle { inner })
}
/// Change if or when [`DeviceEvent`]s are captured.
@@ -456,6 +437,17 @@ impl ActiveEventLoop {
self.p.listen_device_events(allowed);
}
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
pub fn system_theme(&self) -> Option<Theme> {
self.p.system_theme()
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
@@ -468,7 +460,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`LoopExiting`](Event::LoopExiting).
/// See [`LoopExiting`][Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -477,7 +469,7 @@ impl ActiveEventLoop {
/// Returns if the [`EventLoop`] is about to stop.
///
/// See [`exit()`](Self::exit).
/// See [`exit()`][Self::exit].
pub fn exiting(&self) -> bool {
self.p.exiting()
}
@@ -486,9 +478,7 @@ impl ActiveEventLoop {
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle {
platform: self.p.owned_display_handle(),
}
OwnedDisplayHandle { platform: self.p.owned_display_handle() }
}
}
@@ -511,7 +501,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// 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
@@ -562,9 +552,7 @@ pub struct EventLoopProxy<T: 'static> {
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
event_loop_proxy: self.event_loop_proxy.clone(),
}
Self { event_loop_proxy: self.event_loop_proxy.clone() }
}
}

View File

@@ -1,5 +1,6 @@
use crate::platform_impl::PlatformIcon;
use std::{error::Error, fmt, io, mem};
use std::error::Error;
use std::{fmt, io, mem};
#[repr(C)]
#[derive(Debug)]
@@ -20,12 +21,7 @@ pub enum BadIcon {
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount {
width: u32,
height: u32,
width_x_height: usize,
pixel_count: usize,
},
DimensionsVsPixelCount { width: u32, height: u32, width_x_height: usize, pixel_count: usize },
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
}
@@ -33,17 +29,19 @@ pub enum BadIcon {
impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
),
BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
} => write!(f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.",
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(
f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
it impossible to interpret as 32bpp RGBA pixels.",
),
BadIcon::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
write!(
f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of \
pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
dimensions, the expected pixel count is {width_x_height:?}.",
)
},
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
}
}
@@ -69,9 +67,7 @@ mod constructors {
impl RgbaIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
@@ -82,11 +78,7 @@ mod constructors {
pixel_count,
})
} else {
Ok(RgbaIcon {
rgba,
width,
height,
})
Ok(RgbaIcon { rgba, width, height })
}
}
}
@@ -120,8 +112,6 @@ impl Icon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
Ok(Icon {
inner: PlatformIcon::from_rgba(rgba, width, height)?,
})
Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? })
}
}

View File

@@ -106,23 +106,23 @@ impl std::fmt::Debug for NativeKeyCode {
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
}
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
}
debug_tuple.finish()
}
@@ -162,27 +162,27 @@ impl std::fmt::Debug for NativeKey {
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
}
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
}
},
Web(code) => {
debug_tuple = f.debug_tuple("Web");
debug_tuple.field(code);
}
},
}
debug_tuple.finish()
}
@@ -218,7 +218,7 @@ impl PartialEq<NativeKeyCode> for NativeKey {
/// Represents the location of a physical key.
///
/// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified)
/// This type is a superset of [`KeyCode`], including an [`Unidentified`][Self::Unidentified]
/// variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -442,7 +442,8 @@ pub enum KeyCode {
Tab,
/// Japanese: <kbd>変</kbd> (henkan)
Convert,
/// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd> (katakana/hiragana/romaji)
/// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd>
/// (katakana/hiragana/romaji)
KanaMode,
/// Korean: HangulMode <kbd>한/영</kbd> (han/yeong)
///
@@ -490,7 +491,8 @@ pub enum KeyCode {
NumLock,
/// <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control
Numpad0,
/// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or remote control
/// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or remote
/// control
Numpad1,
/// <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control
Numpad2,
@@ -794,13 +796,14 @@ pub enum NamedKey {
// Legacy modifier key.
Hyper,
/// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard
/// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key.
/// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘`
/// key.
///
/// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key.
Super,
/// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key
/// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for
/// the Android `KEYCODE_DPAD_CENTER`.
/// The `Enter` or `↵` key. Used to activate current selection or accept current input. This
/// key value is also used for the `Return` (Macintosh numpad) key. This key value is also
/// used for the Android `KEYCODE_DPAD_CENTER`.
Enter,
/// The Horizontal Tabulation `Tab` key.
Tab,
@@ -836,8 +839,8 @@ pub enum NamedKey {
CrSel,
/// Cut the current selection. (`APPCOMMAND_CUT`)
Cut,
/// Used to delete the character to the right of the cursor. This key value is also used for the
/// key labeled `Delete` on MacOS keyboards when `Fn` is active.
/// Used to delete the character to the right of the cursor. This key value is also used for
/// the key labeled `Delete` on MacOS keyboards when `Fn` is active.
Delete,
/// The Erase to End of Field key. This key deletes all characters from the current cursor
/// position to the end of the current field.
@@ -921,8 +924,8 @@ pub enum NamedKey {
/// their code points.
CodeInput,
/// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a
/// manner similar to a dead key, triggering a mode where subsequent key presses are combined to
/// produce a different character.
/// manner similar to a dead key, triggering a mode where subsequent key presses are combined
/// to produce a different character.
Compose,
/// Convert the current input method sequence.
Convert,
@@ -961,9 +964,9 @@ pub enum NamedKey {
/// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from
/// romaji mode).
KanaMode,
/// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is
/// typically used to switch to a hiragana keyboard for the purpose of converting input into
/// kanji. (`KEYCODE_KANA`)
/// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key
/// is typically used to switch to a hiragana keyboard for the purpose of converting input
/// into kanji. (`KEYCODE_KANA`)
KanjiMode,
/// The Katakana (Japanese Kana characters) key.
Katakana,
@@ -1588,7 +1591,7 @@ impl Key {
/// # Examples
///
/// ```
/// use winit::keyboard::{NamedKey, Key};
/// use winit::keyboard::{Key, NamedKey};
///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
@@ -1610,7 +1613,8 @@ impl Key {
/// keys can be above the letters or on the numpad. This enum allows the user to differentiate
/// them.
///
/// See the documentation for the [`location`] field on the [`KeyEvent`] struct for more information.
/// See the documentation for the [`location`] field on the [`KeyEvent`] struct for more
/// information.
///
/// [`location`]: ../event/struct.KeyEvent.html#structfield.location
/// [`KeyEvent`]: crate::event::KeyEvent
@@ -1619,8 +1623,8 @@ impl Key {
pub enum KeyLocation {
/// The key is in its "normal" location on the keyboard.
///
/// For instance, the "1" key above the "Q" key on a QWERTY keyboard will use this location. This
/// invariant is also returned when the location of the key cannot be identified.
/// For instance, the "1" key above the "Q" key on a QWERTY keyboard will use this location.
/// This invariant is also returned when the location of the key cannot be identified.
///
/// ![Standard 1 key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_standard_1_key.svg)
///
@@ -1703,14 +1707,17 @@ impl ModifiersState {
pub fn shift_key(&self) -> bool {
self.intersects(Self::SHIFT)
}
/// Returns `true` if the control key is pressed.
pub fn control_key(&self) -> bool {
self.intersects(Self::CONTROL)
}
/// Returns `true` if the alt key is pressed.
pub fn alt_key(&self) -> bool {
self.intersects(Self::ALT)
}
/// Returns `true` if the super key is pressed.
pub fn super_key(&self) -> bool {
self.intersects(Self::SUPER)
@@ -1784,12 +1791,8 @@ mod modifiers_serde {
where
D: Deserializer<'de>,
{
let ModifiersStateSerialize {
shift_key,
control_key,
alt_key,
super_key,
} = ModifiersStateSerialize::deserialize(deserializer)?;
let ModifiersStateSerialize { shift_key, control_key, alt_key, super_key } =
ModifiersStateSerialize::deserialize(deserializer)?;
let mut m = ModifiersState::empty();
m.set(ModifiersState::SHIFT, shift_key);
m.set(ModifiersState::CONTROL, control_key);

View File

@@ -7,7 +7,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`].
@@ -19,33 +24,22 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired.
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if
//! desired.
//!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on
//! most other platforms. However, this model can be re-implemented to an extent with
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works
//! poorly on most other platforms. However, this model can be re-implemented to an extent with
#![cfg_attr(
any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform),
doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]"
)]
#![cfg_attr(
not(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
not(any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform)),
doc = "`EventLoopExtPumpEvents::pump_app_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
@@ -95,19 +89,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
@@ -116,16 +113,16 @@
//!
//! # Drawing on the window
//!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you
//! to retrieve the raw handle of the window and display (see the [`platform`] module and/or the
//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows
//! you to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
//!
//! Note that many platforms will display garbage data in the window's client area if the
//! application doesn't render anything to the window by the time the desktop compositor is ready to
//! display the window to the user. If you notice this happening, you should create the window with
//! [`visible` set to `false`](crate::window::WindowAttributes::with_visible) and explicitly make the
//! window visible only once you're ready to render into it.
//! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
//! the window visible only once you're ready to render into it.
//!
//! # UI scaling
//!
@@ -151,13 +148,11 @@
//! Winit provides the following Cargo features:
//!
//! * `x11` (enabled by default): On Unix platforms, enables the X11 backend.
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland
//! backend.
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland backend.
//! * `rwh_04`: Implement `raw-window-handle v0.4` traits.
//! * `rwh_05`: Implement `raw-window-handle v0.5` traits.
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
//! * `serde`: Enables serialization/deserialization of certain types with
//! [Serde](https://crates.io/crates/serde).
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//!
//! See the [`platform`] module for documentation on platform-specific cargo
@@ -186,14 +181,15 @@
#![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(clippy, deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly
// doc
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)]
#[cfg(feature = "rwh_04")]
pub use rwh_04 as raw_window_handle_04;
#[cfg(feature = "rwh_05")]
pub use rwh_05 as raw_window_handle_05;
#[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle;

View File

@@ -3,12 +3,10 @@
//! 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 crate::{
dpi::{PhysicalPosition, PhysicalSize},
platform_impl,
};
//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors].
//! - [`Window::available_monitors`][crate::window::Window::available_monitors].
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl;
/// Deprecated! Use `VideoModeHandle` instead.
#[deprecated = "Renamed to `VideoModeHandle`"]
@@ -79,9 +77,7 @@ impl VideoModeHandle {
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle {
inner: self.video_mode.monitor(),
}
MonitorHandle { inner: self.video_mode.monitor() }
}
}
@@ -166,8 +162,6 @@ impl MonitorHandle {
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner
.video_modes()
.map(|video_mode| VideoModeHandle { video_mode })
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
}
}

View File

@@ -19,6 +19,7 @@
//!
//! | winit | ndk-glue |
//! | :---: | :--------------------------: |
//! | 0.30 | `android-activity = "0.6"` |
//! | 0.29 | `android-activity = "0.5"` |
//! | 0.28 | `android-activity = "0.4"` |
//! | 0.27 | `ndk-glue = "0.7"` |
@@ -58,26 +59,39 @@
//!
//! ## Converting from `ndk-glue` to `android-activity`
//!
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
//! 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.29.15", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.7",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above).
use crate::{
event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder},
window::{Window, WindowAttributes},
};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::window::{Window, WindowAttributes};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {}
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
impl<T> EventLoopExtAndroid for EventLoop<T> {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {}
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
@@ -96,7 +110,11 @@ impl WindowExtAndroid for Window {
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {
fn android_app(&self) -> &AndroidApp {
&self.p.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
@@ -104,9 +122,9 @@ pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the `AndroidApp` is not global state.
/// This must be called on Android since the [`AndroidApp`] is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
@@ -143,10 +161,10 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the `AndroidApp` type for
/// For compatibility applications should then import the [`AndroidApp`] type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os="android")]
/// #[cfg(target_os = "android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {

View File

@@ -66,11 +66,9 @@
use std::os::raw::c_void;
use crate::{
event_loop::EventLoop,
monitor::{MonitorHandle, VideoModeHandle},
window::{Window, WindowAttributes},
};
use crate::event_loop::EventLoop;
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
@@ -157,6 +155,21 @@ pub trait WindowExtIOS {
/// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool);
/// Sets whether the [`Window`] should recognize pan gestures.
///
/// The default is to not recognize gestures.
/// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view
///
/// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches)
///
/// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches)
fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
);
/// Sets whether the [`Window`] should recognize double tap gestures.
///
/// The default is to not recognize gestures.
@@ -171,20 +184,17 @@ pub trait WindowExtIOS {
impl WindowExtIOS for Window {
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
self.window
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
}
#[inline]
@@ -196,32 +206,43 @@ impl WindowExtIOS for Window {
#[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))
self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
}
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
self.window.maybe_queue_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));
self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
#[inline]
fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
self.window.maybe_queue_on_main(move |w| {
w.recognize_pan_gesture(
should_recognize,
minimum_number_of_touches,
maximum_number_of_touches,
)
});
}
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
self.window.maybe_queue_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));
self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
}
}
@@ -301,8 +322,7 @@ impl WindowAttributesExtIOS for WindowAttributes {
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
self
}
@@ -336,15 +356,13 @@ impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle {
video_mode: self.inner.preferred_video_mode(),
}
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
}
}

View File

@@ -19,11 +19,9 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
};
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
@@ -96,6 +94,12 @@ 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;
}
impl WindowExtMacOS for Window {
@@ -106,8 +110,7 @@ impl WindowExtMacOS for Window {
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
@@ -117,14 +120,12 @@ impl WindowExtMacOS for Window {
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
self.window
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
self.window
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
@@ -144,8 +145,7 @@ impl WindowExtMacOS for Window {
#[inline]
fn select_tab_at_index(&self, index: usize) {
self.window
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
}
#[inline]
@@ -160,20 +160,28 @@ impl WindowExtMacOS for Window {
#[inline]
fn set_document_edited(&self, edited: bool) {
self.window
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
self.window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_borderless_game())
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -192,7 +200,8 @@ pub enum ActivationPolicy {
/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowAttributes::with_decorations`] method:
/// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method:
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
@@ -223,6 +232,8 @@ 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;
}
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -282,9 +293,7 @@ impl WindowAttributesExtMacOS for WindowAttributes {
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific
.tabbing_identifier
.replace(tabbing_identifier.to_string());
self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
@@ -293,12 +302,21 @@ 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
}
}
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
///
@@ -307,7 +325,7 @@ pub trait EventLoopBuilderExtMacOS {
/// ```
/// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
/// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
///
/// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")]
@@ -350,7 +368,7 @@ pub trait EventLoopBuilderExtMacOS {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy;
self.platform_specific.activation_policy = Some(activation_policy);
self
}
@@ -383,18 +401,18 @@ impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() };
self.inner
.ns_screen(mtm)
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
pub trait ActiveEventLoopExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with Command-H.
/// Hide the entire application. In most applications this is typically triggered with
/// Command-H.
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
/// Hide the other applications. In most applications this is typically triggered with
/// Command+Option-H.
fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs.
///

View File

@@ -51,11 +51,5 @@ pub mod pump_events;
))]
pub mod modifier_supplement;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
docsrs
))]
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
pub mod scancode;

View File

@@ -25,10 +25,7 @@ pub trait KeyEventExtModifierSupplement {
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific
.text_with_all_modifiers
.as_ref()
.map(|s| s.as_str())
self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str())
}
#[inline]

View File

@@ -52,18 +52,18 @@ pub trait EventLoopExtPumpEvents {
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
@@ -74,22 +74,21 @@ pub trait EventLoopExtPumpEvents {
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events are delivered via callbacks based on an event loop that is internal to the browser
/// itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
/// - **Windows**: The implementation will use `PeekMessage` when checking for
/// window messages to avoid blocking your external event loop.
/// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
/// to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global application
/// whenever the application `RunLoop` indicates that it is preparing to block
/// and wait for new events.
/// - **MacOS**: The implementation works in terms of stopping the global application whenever
/// the application `RunLoop` indicates that it is preparing to block and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private

View File

@@ -21,8 +21,8 @@ pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) closures
/// and it is possible to return control back to the caller without
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// closures and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
///
@@ -42,7 +42,9 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// backend it is possible to use `EventLoopExtWebSys::spawn()`
#[cfg_attr(not(web_platform), doc = "[^1]")]
/// more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -55,17 +57,13 @@ pub trait EventLoopExtRunOnDemand {
/// - Android
///
/// # Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new events. Events
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
///
#[cfg_attr(
not(web_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
#[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")]
#[rustfmt::skip]
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()

View File

@@ -2,8 +2,8 @@ use crate::keyboard::{KeyCode, PhysicalKey};
// TODO: Describe what this value contains for each platform
/// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific
/// scancode.
/// Additional methods for the [`PhysicalKey`] type that allow the user to access the
/// platform-specific scancode.
///
/// [`PhysicalKey`]: crate::keyboard::PhysicalKey
pub trait PhysicalKeyExtScancode {
@@ -23,7 +23,7 @@ pub trait PhysicalKeyExtScancode {
///
/// ## Platform-specific
/// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract
/// `8` to get the value you wanted.
/// `8` to get the value you wanted.
fn from_scancode(scancode: u32) -> PhysicalKey;
}

View File

@@ -13,11 +13,9 @@
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
pub use crate::window::Theme;
@@ -34,6 +32,19 @@ impl ActiveEventLoopExtWayland for ActiveEventLoop {
}
}
/// Additional methods on [`EventLoop`] that are specific to Wayland.
pub trait EventLoopExtWayland {
/// True if the [`EventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
pub trait EventLoopBuilderExtWayland {
/// Force using Wayland.
@@ -80,10 +91,8 @@ pub trait WindowAttributesExtWayland {
impl WindowAttributesExtWayland for WindowAttributes {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self.platform_specific.name =
Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
self
}
}

View File

@@ -30,8 +30,8 @@
//! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`],
//! and [`WindowEvent::Touch`].
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and
//! [`WindowEvent::Touch`].
//! - [`Window::set_outer_position()`]
//!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
@@ -109,11 +109,7 @@ pub trait WindowAttributesExtWebSys {
/// In any case, the canvas won't be automatically inserted into the web page.
///
/// [`None`] by default.
#[cfg_attr(
not(web_platform),
doc = "",
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
)]
#[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Sets whether `event.preventDefault()` should be called on events on the
@@ -166,10 +162,7 @@ pub trait EventLoopExtWebSys {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(
all(web_platform, target_feature = "exception-handling"),
doc = "`run_app()`"
)]
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]"
@@ -181,6 +174,7 @@ pub trait EventLoopExtWebSys {
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
#[rustfmt::skip]
///
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
@@ -196,6 +190,34 @@ pub trait EventLoopExtWebSys {
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn set_poll_strategy(&self, strategy: PollStrategy);
/// Gets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
@@ -213,6 +235,22 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
{
self.event_loop.spawn(event_handler)
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.event_loop.set_poll_strategy(strategy);
}
fn poll_strategy(&self) -> PollStrategy {
self.event_loop.poll_strategy()
}
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.event_loop.set_wait_until_strategy(strategy);
}
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.event_loop.wait_until_strategy()
}
}
pub trait ActiveEventLoopExtWebSys {
@@ -230,6 +268,20 @@ pub trait ActiveEventLoopExtWebSys {
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
/// cursor has completely finished loading.
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
@@ -250,9 +302,19 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy()
}
#[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.p.set_wait_until_strategy(strategy);
}
#[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.p.wait_until_strategy()
}
}
/// Strategy used for [`ControlFlow::Poll`](crate::event_loop::ControlFlow::Poll).
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PollStrategy {
/// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available
@@ -278,6 +340,29 @@ pub enum PollStrategy {
Scheduler,
}
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WaitUntilStrategy {
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy is commonly not affected by browser throttling unless the window is not
/// focused.
///
/// This is the default strategy.
///
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
#[default]
Scheduler,
/// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker].
///
/// This strategy is commonly not affected by browser throttling regardless of window focus.
///
/// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
Worker,
}
pub trait CustomCursorExtWebSys {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
@@ -303,13 +388,7 @@ impl CustomCursorExtWebSys for CustomCursor {
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource {
inner: PlatformCustomCursorSource::Url {
url,
hotspot_x,
hotspot_y,
},
}
CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
}
fn from_animation(
@@ -343,7 +422,7 @@ impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animtion"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
@@ -360,9 +439,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0)
.poll(cx)
.map_ok(|cursor| CustomCursor { inner: cursor })
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
}
}
@@ -378,10 +455,11 @@ impl Display for CustomCursorError {
match self {
Self::Blob => write!(f, "failed to create `Blob`"),
Self::Decode(error) => write!(f, "failed to decode image: {error}"),
Self::Animation => write!(
f,
"found `CustomCursor` that is an animation when building an animation"
),
Self::Animation => {
write!(f, "found `CustomCursor` that is an animation when building an animation")
},
}
}
}
impl Error for CustomCursorError {}

View File

@@ -2,15 +2,15 @@
//!
//! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly.
use std::{ffi::c_void, path::Path};
use std::borrow::Borrow;
use std::ffi::c_void;
use std::path::Path;
use crate::{
dpi::PhysicalSize,
event::DeviceId,
event_loop::EventLoopBuilder,
monitor::MonitorHandle,
window::{BadIcon, Icon, Window, WindowAttributes},
};
use crate::dpi::PhysicalSize;
use crate::event::DeviceId;
use crate::event_loop::EventLoopBuilder;
use crate::monitor::MonitorHandle;
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
/// Window Handle type used by Win32 API
pub type HWND = isize;
@@ -57,11 +57,11 @@ pub enum BackdropType {
pub struct Color(u32);
impl Color {
// Special constant only valid for the window border and therefore modeled using Option<Color>
// for user facing code
const NONE: Color = Color(0xfffffffe);
/// Use the system's default color
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF);
//Special constant only valid for the window border and therefore modeled using Option<Color> for user facing code
const NONE: Color = Color(0xFFFFFFFE);
pub const SYSTEM_DEFAULT: Color = Color(0xffffffff);
/// Create a new color from the given RGB values
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
@@ -105,6 +105,60 @@ pub enum CornerPreference {
RoundSmall = 3,
}
/// A wrapper around a [`Window`] that ignores thread-specific window handle limitations.
///
/// See [`WindowBorrowExtWindows::any_thread`] for more information.
#[derive(Debug)]
pub struct AnyThread<W>(W);
impl<W: Borrow<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 {
&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;
fn deref(&self) -> &Self::Target {
self.get_ref()
}
}
#[cfg(feature = "rwh_06")]
impl<W: Borrow<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() }
}
}
/// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread.
@@ -202,8 +256,8 @@ pub trait WindowExtWindows {
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must enable
/// the owner window before destroying the dialog box.
/// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must
/// enable the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
/// If a child window is disabled, it is ignored when the system tries to determine which
@@ -248,6 +302,60 @@ pub trait WindowExtWindows {
///
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
/// Get the raw window handle for this [`Window`] without checking for thread affinity.
///
/// Window handles in Win32 have a property called "thread affinity" that ties them to their
/// origin thread. Some operations can only happen on the window's origin thread, while others
/// can be called from any thread. For example, [`SetWindowSubclass`] is not thread safe while
/// [`GetDC`] is thread safe.
///
/// In Rust terms, the window handle is `Send` sometimes but `!Send` other times.
///
/// Therefore, in order to avoid confusing threading errors, [`Window`] only returns the
/// window handle when the [`window_handle`] function is called from the thread that created
/// the window. In other cases, it returns an [`Unavailable`] error.
///
/// However in some cases you may already know that you are using the window handle for
/// operations that are guaranteed to be thread-safe. In which case this function aims
/// to provide an escape hatch so these functions are still accessible from other threads.
///
/// # Safety
///
/// It is the responsibility of the user to only pass the window handle into thread-safe
/// Win32 APIs.
///
/// [`SetWindowSubclass`]: https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-setwindowsubclass
/// [`GetDC`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc
/// [`Window`]: crate::window::Window
/// [`window_handle`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/trait.HasWindowHandle.html#tymethod.window_handle
/// [`Unavailable`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/enum.HandleError.html#variant.Unavailable
///
/// ## Example
///
/// ```no_run
/// # use winit::window::Window;
/// # fn scope(window: Window) {
/// use std::thread;
/// use winit::platform::windows::WindowExtWindows;
/// use winit::raw_window_handle::HasWindowHandle;
///
/// // We can get the window handle on the current thread.
/// let handle = window.window_handle().unwrap();
///
/// // However, on another thread, we can't!
/// thread::spawn(move || {
/// assert!(window.window_handle().is_err());
///
/// // We can use this function as an escape hatch.
/// let handle = unsafe { window.window_handle_any_thread().unwrap() };
/// });
/// # }
/// ```
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
}
impl WindowExtWindows for Window {
@@ -283,10 +391,10 @@ impl WindowExtWindows for Window {
#[inline]
fn set_title_background_color(&self, color: Option<Color>) {
// 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))
// 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))
}
#[inline]
@@ -298,15 +406,57 @@ impl WindowExtWindows for Window {
fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference)
}
#[cfg(feature = "rwh_06")]
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
unsafe {
let handle = self.window.rwh_06_no_thread_check()?;
// SAFETY: The handle is valid in this context.
Ok(rwh_06::WindowHandle::borrow_raw(handle))
}
}
}
/// Additional methods for anything that dereference to [`Window`].
///
/// [`Window`]: crate::window::Window
pub trait WindowBorrowExtWindows: Borrow<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
/// affinity limitations. However, it may be desired to pass the [`Window`] into something
/// that requires the [`HasWindowHandle`] trait, while ignoring thread affinity limitations.
///
/// This function wraps anything that implements `Borrow<Window>` into a structure that
/// uses the inner window handle as a mean of implementing [`HasWindowHandle`]. It wraps
/// `Window`, `&Window`, `Arc<Window>`, and other reference types.
///
/// # Safety
///
/// It is the responsibility of the user to only pass the window handle into thread-safe
/// Win32 APIs.
///
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
/// [`Window`]: crate::window::Window
/// [`HasWindowHandle`]: rwh_06::HasWindowHandle
unsafe fn any_thread(self) -> AnyThread<Self> {
AnyThread(self)
}
}
impl<W: Borrow<Window> + Sized> WindowBorrowExtWindows for W {}
/// Additional methods on `WindowAttributes` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowAttributesExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable)
/// on the owner window to create a modal dialog box.
/// Can be used in combination with
/// [`WindowExtWindows::set_enable(false)`][WindowExtWindows::set_enable] on the owner
/// window to create a modal dialog box.
///
/// From MSDN:
/// - An owned window is always above its owner in the z-order.
@@ -322,17 +472,14 @@ pub trait WindowAttributesExtWindows {
///
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
///
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look.
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect.
///
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how
/// the menus look. If you use this, it is recommended that you combine it with
/// `with_theme(Some(Theme::Light))` to avoid a jarring effect.
#[cfg_attr(
platform_windows,
windows_platform,
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)]
#[cfg_attr(
not(platform_windows),
doc = "[`CreateMenu`]: #only-available-on-windows"
)]
#[cfg_attr(not(windows_platform), doc = "[`CreateMenu`]: #only-available-on-windows")]
fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -341,12 +488,12 @@ pub trait WindowAttributesExtWindows {
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future.
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions.
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
/// Enables or disables drag and drop support (enabled by default). Will interfere with other
/// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED`
/// instead of `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still
/// attempt to initialize COM API regardless of this option. Currently only fullscreen mode
/// does that, but there may be more in the future. If you need COM API with
/// `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
fn with_drag_and_drop(self, flag: bool) -> Self;
/// Whether show or hide the window icon in the taskbar.

View File

@@ -2,11 +2,9 @@
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
event_loop::{ActiveEventLoop, EventLoopBuilder},
monitor::MonitorHandle,
window::{Window, WindowAttributes},
};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
use crate::dpi::Size;
@@ -15,11 +13,12 @@ use crate::dpi::Size;
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
/// root window clicks.
/// A desktop feature. This can include a single window containing desktop icons with the same
/// dimensions as the screen, allowing the desktop environment to have full control of the
/// desktop, without the need for proxying root window clicks.
Desktop,
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all
/// other windows.
Dock,
/// Toolbar windows. "Torn off" from the main application.
Toolbar,
@@ -37,8 +36,8 @@ pub enum WindowType {
/// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows.
PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
/// This property is typically used on override-redirect windows.
/// A tooltip window. Usually used to show additional information when hovering over an object
/// with the cursor. This property is typically used on override-redirect windows.
Tooltip,
/// The window is a notification.
/// This property is typically used on override-redirect windows.
@@ -46,7 +45,7 @@ pub enum WindowType {
/// This should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows.
Combo,
/// This indicates the the window is being dragged.
/// This indicates the window is being dragged.
/// This property is typically used on override-redirect windows.
Dnd,
/// This is a normal, top-level window.
@@ -82,12 +81,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.
@@ -103,6 +97,19 @@ impl ActiveEventLoopExtX11 for ActiveEventLoop {
}
}
/// Additional methods on [`EventLoop`] that are specific to X11.
pub trait EventLoopExtX11 {
/// True if the [`EventLoop`] uses X11.
fn is_x11(&self) -> bool;
}
impl<T: 'static> EventLoopExtX11 for EventLoop<T> {
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to X11.
pub trait EventLoopBuilderExtX11 {
/// Force using X11.
@@ -144,7 +151,8 @@ pub trait WindowAttributesExtX11 {
/// Build window with the given `general` and `instance` names.
///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`.
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance",
/// "general"`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
@@ -202,10 +210,8 @@ impl WindowAttributesExtX11 for WindowAttributes {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new(
general.into(),
instance.into(),
));
self.platform_specific.name =
Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
self
}

View File

@@ -1,7 +1,5 @@
use android_activity::{
input::{KeyAction, KeyEvent, KeyMapChar, Keycode},
AndroidApp,
};
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use android_activity::AndroidApp;
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
@@ -105,7 +103,7 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::VolumeUp => KeyCode::AudioVolumeUp,
Keycode::VolumeDown => KeyCode::AudioVolumeDown,
Keycode::VolumeMute => KeyCode::AudioVolumeMute,
//Keycode::Mute => None, // Microphone mute
// Keycode::Mute => None, // Microphone mute
Keycode::MediaPlayPause => KeyCode::MediaPlayPause,
Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext,
@@ -176,7 +174,7 @@ pub fn character_map_and_combine_key(
Err(err) => {
tracing::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}");
return None;
}
},
};
match key_map.get(key_event.key_code(), key_event.meta_state()) {
@@ -188,9 +186,12 @@ pub fn character_map_and_combine_key(
Ok(Some(key)) => Some(key),
Ok(None) => None,
Err(err) => {
tracing::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
tracing::warn!(
"KeyEvent: Failed to combine 'dead key' accent '{accent}' with \
'{unicode}': {err:?}"
);
None
}
},
}
} else {
Some(unicode)
@@ -200,23 +201,23 @@ pub fn character_map_and_combine_key(
} else {
Some(KeyMapChar::Unicode(unicode))
}
}
},
Ok(KeyMapChar::CombiningAccent(accent)) => {
if key_event.action() == KeyAction::Down {
*combining_accent = Some(accent);
}
Some(KeyMapChar::CombiningAccent(accent))
}
},
Ok(KeyMapChar::None) => {
// Leave any combining_accent state in tact (seems to match how other
// Android apps work)
None
}
},
Err(err) => {
tracing::warn!("KeyEvent: Failed to get key map character: {err:?}");
*combining_accent = None;
None
}
},
}
}

View File

@@ -1,16 +1,10 @@
#![cfg(android_platform)]
use std::{
cell::Cell,
collections::VecDeque,
hash::Hash,
marker::PhantomData,
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex,
},
time::{Duration, Instant},
};
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
@@ -18,24 +12,24 @@ use android_activity::{
};
use tracing::{debug, trace, warn};
use crate::{
cursor::Cursor,
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error,
event::{self, Force, InnerSizeWriter, StartCause},
event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents},
platform::pump_events::PumpStatus,
window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
},
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
use crate::error::EventLoopError;
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
};
use crate::{error::EventLoopError, platform_impl::Fullscreen};
mod keycodes;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@@ -44,9 +38,7 @@ static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| {
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
struct PeekableReceiver<T> {
@@ -58,6 +50,7 @@ impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: mpsc::Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
@@ -66,14 +59,15 @@ impl<T> PeekableReceiver<T> {
Ok(v) => {
self.first = Some(v);
true
}
},
Err(mpsc::TryRecvError::Empty) => false,
Err(mpsc::TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
}
},
}
}
pub fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
@@ -88,9 +82,7 @@ struct SharedFlagSetter {
}
impl SharedFlagSetter {
pub fn set(&self) -> bool {
self.flag
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
}
}
@@ -104,15 +96,13 @@ struct SharedFlag {
// was queued and be able to read and clear the state atomically)
impl SharedFlag {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
Self { flag: Arc::new(AtomicBool::new(false)) }
}
pub fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter {
flag: self.flag.clone(),
}
SharedFlagSetter { flag: self.flag.clone() }
}
pub fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
}
@@ -126,11 +116,9 @@ pub struct RedrawRequester {
impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester {
flag: flag.setter(),
waker,
}
RedrawRequester { flag: flag.setter(), waker }
}
pub fn request_redraw(&self) {
if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag
@@ -144,11 +132,11 @@ impl RedrawRequester {
pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> {
android_app: AndroidApp,
pub(crate) android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent
user_events_receiver: PeekableReceiver<T>, // must wake looper whenever something gets sent
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
@@ -165,10 +153,7 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self {
android_app: Default::default(),
ignore_volume_keys: true,
}
Self { android_app: Default::default(), ignore_volume_keys: true }
}
}
@@ -178,7 +163,10 @@ impl<T: 'static> EventLoop<T> {
) -> Result<Self, EventLoopError> {
let (user_events_sender, user_events_receiver) = mpsc::channel();
let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android");
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 redraw_flag = SharedFlag::new();
Ok(Self {
@@ -225,15 +213,15 @@ impl<T: 'static> EventLoop<T> {
match event {
MainEvent::InitWindow { .. } => {
callback(event::Event::Resumed, self.window_target());
}
},
MainEvent::TerminateWindow { .. } => {
callback(event::Event::Suspended, self.window_target());
}
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change");
}
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
callback(
@@ -243,7 +231,7 @@ impl<T: 'static> EventLoop<T> {
},
self.window_target(),
);
}
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
callback(
@@ -253,7 +241,7 @@ impl<T: 'static> EventLoop<T> {
},
self.window_target(),
);
}
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
let old_scale_factor = monitor.scale_factor();
@@ -273,43 +261,43 @@ impl<T: 'static> EventLoop<T> {
};
callback(event, self.window_target());
}
}
},
MainEvent::LowMemory => {
callback(event::Event::MemoryWarning, self.window_target());
}
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStart notification to application");
}
},
MainEvent::Resume { .. } => {
debug!("App Resumed - is running");
self.running = true;
}
},
MainEvent::SaveState { .. } => {
// XXX: how to forward this state to applications?
// XXX: also how do we expose state restoration to apps?
warn!("TODO: forward saveState notification to application");
}
},
MainEvent::Pause => {
debug!("App Paused - stopped running");
self.running = false;
}
},
MainEvent::Stop => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStop notification to application");
}
},
MainEvent::Destroy => {
// XXX: maybe exit mainloop to drop things before being
// killed by the OS?
warn!("TODO: forward onDestroy notification to application");
}
},
MainEvent::InsetsChanged { .. } => {
// XXX: how to forward this state to applications?
warn!("TODO: handle Android InsetsChanged notification");
}
},
unknown => {
trace!("Unknown MainEvent {unknown:?} (ignored)");
}
},
}
} else {
trace!("No main event to handle");
@@ -331,7 +319,7 @@ impl<T: 'static> EventLoop<T> {
},
Err(err) => {
tracing::warn!("Failed to get input events iterator: {err:?}");
}
},
}
// Empty the user event buffer
@@ -392,13 +380,13 @@ impl<T: 'static> EventLoop<T> {
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
}
},
};
if let Some(phase) = phase {
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
@@ -407,18 +395,19 @@ impl<T: 'static> EventLoop<T> {
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:?}");
let location =
PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
trace!(
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
@@ -432,17 +421,18 @@ impl<T: 'static> EventLoop<T> {
callback(event, self.window_target());
}
}
}
},
InputEvent::KeyEvent(key) => {
match key.key_code() {
// Flag keys related to volume as unhandled. While winit does not have a way for applications
// to configure what keys to flag as handled, this appears to be a good default until winit
// Flag keys related to volume as unhandled. While winit does not have a way for
// applications to configure what keys to flag as handled,
// this appears to be a good default until winit
// can be configured.
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => {
if self.ignore_volume_keys {
input_status = InputStatus::Unhandled
}
}
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute
if self.ignore_volume_keys =>
{
input_status = InputStatus::Unhandled
},
keycode => {
let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed,
@@ -473,12 +463,12 @@ impl<T: 'static> EventLoop<T> {
},
};
callback(event, self.window_target());
}
},
}
}
},
_ => {
warn!("Unknown android_activity input event {event:?}")
}
},
}
input_status
@@ -499,13 +489,13 @@ impl<T: 'static> EventLoop<T> {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
}
},
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
}
},
_ => {
continue;
}
},
}
}
}
@@ -561,7 +551,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
},
};
min_timeout(control_flow_timeout, timeout)
@@ -574,8 +564,9 @@ impl<T: 'static> EventLoop<T> {
match poll_event {
android_activity::PollEvent::Wake => {
// In the X11 backend it's noted that too many false-positive wake ups
// would cause the event loop to run continuously. They handle this by re-checking
// for pending events (assuming they cover all valid reasons for a wake up).
// would cause the event loop to run continuously. They handle this by
// re-checking for pending events (assuming they cover all
// valid reasons for a wake up).
//
// For now, user_events and redraw_requests are the only reasons to expect
// a wake up here so we can ignore the wake up if there are no events/requests.
@@ -586,35 +577,26 @@ impl<T: 'static> EventLoop<T> {
{
return;
}
}
android_activity::PollEvent::Timeout => {}
},
android_activity::PollEvent::Timeout => {},
android_activity::PollEvent::Main(event) => {
main_event = Some(event);
}
},
unknown_event => {
warn!("Unknown poll event {unknown_event:?} (ignored)");
}
},
}
self.cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}
StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
} else {
StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}
StartCause::ResumeTimeReached { start, requested_resume: deadline }
}
}
},
};
self.single_iteration(main_event, &mut callback);
@@ -657,16 +639,14 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|err| event_loop::EventLoopClosed(err.0))?;
self.user_events_sender.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?;
self.waker.wake();
Ok(())
}
}
pub struct ActiveEventLoop {
app: AndroidApp,
pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
@@ -679,9 +659,7 @@ impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor {
inner: PlatformCustomCursor,
}
CustomCursor { inner: PlatformCustomCursor }
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
@@ -699,14 +677,17 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
))
Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -798,10 +779,7 @@ impl Window {
) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes
Ok(Self {
app: el.app.clone(),
redraw_requester: el.redraw_requester.clone(),
})
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) {
@@ -930,7 +908,13 @@ impl Window {
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
}
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
@@ -941,41 +925,31 @@ impl Window {
pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
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(),
))
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(),
))
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(),
))
Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
}
#[cfg(feature = "rwh_04")]
@@ -985,7 +959,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
panic!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
}
}
@@ -996,7 +974,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
panic!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
}
}
@@ -1014,7 +996,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle()
} else {
tracing::error!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
tracing::error!(
"Cannot get the native window, it's null and will always be null before \
Event::Resumed and after Event::Suspended. Make sure you only call this function \
between those events."
);
Err(rwh_06::HandleError::Unavailable)
}
}
@@ -1023,9 +1009,7 @@ impl Window {
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
))
Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
}
pub fn config(&self) -> ConfigurationRef {
@@ -1102,11 +1086,7 @@ impl MonitorHandle {
}
pub fn scale_factor(&self) -> f64 {
self.app
.config()
.density()
.map(|dpi| dpi as f64 / 160.0)
.unwrap_or(1.0)
self.app.config().density().map(|dpi| dpi as f64 / 160.0).unwrap_or(1.0)
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {

View File

@@ -0,0 +1,369 @@
#![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};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
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>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RequestError> {
let mtm = window_target.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_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WindowDelegate) -> R + Send,
) -> R {
self.delegate.get_on_main(|delegate| f(delegate))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.delegate.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
}
}
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None);
}
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
#[cfg(feature = "rwh_06")]
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)) }
}
}
#[cfg(feature = "rwh_06")]
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 inner_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position()))
}
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
Ok(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 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 })
})
}
#[cfg(feature = "rwh_06")]
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
declare_class!(
#[derive(Debug)]
pub struct WinitWindow;
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
}
);
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
}
}

View File

@@ -1,13 +1,9 @@
use icrate::Foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::UIApplication;
use super::app_state::{self, EventWrapper};
use super::uikit::{UIApplication, UIWindow};
use super::window::WinitUIWindow;
use crate::{
event::{Event, WindowEvent},
window::WindowId as RootWindowId,
};
use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper};
use crate::event::Event;
declare_class!(
pub struct AppDelegate;
@@ -42,34 +38,17 @@ declare_class!(
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
app_state::terminated(application);
}
#[method(applicationDidReceiveMemoryWarning:)]
@@ -79,25 +58,3 @@ declare_class!(
}
}
);
impl AppDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -1,14 +1,11 @@
#![deny(unused_results)]
use std::{
cell::{RefCell, RefMut},
collections::HashSet,
fmt, mem,
os::raw::c_void,
ptr,
sync::{Arc, Mutex, OnceLock},
time::Instant,
};
use std::cell::{RefCell, RefMut};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use std::{fmt, mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
@@ -16,21 +13,20 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use icrate::Foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
};
use objc2::rc::Id;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{msg_send, sel};
use super::uikit::UIView;
use super::window::WinitUIWindow;
use crate::{
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow},
window::WindowId as RootWindowId,
use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use super::window::WinitUIWindow;
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;
macro_rules! bug {
($($msg:tt)*) => {
@@ -76,7 +72,7 @@ pub(crate) enum EventWrapper {
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) window: Retained<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
@@ -94,13 +90,7 @@ enum UserCallbackTransitionResult<'a> {
impl Event<HandlePendingUserEvents> {
fn is_redraw(&self) -> bool {
matches!(
self,
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
}
)
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
}
@@ -109,25 +99,25 @@ impl Event<HandlePendingUserEvents> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
NotLaunched {
queued_windows: Vec<Id<WinitUIWindow>>,
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
Launching {
queued_windows: Vec<Id<WinitUIWindow>>,
queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
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<Id<WinitUIWindow>>,
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingRedraws {
handler: EventLoopHandler,
@@ -157,6 +147,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)]
@@ -216,10 +208,7 @@ impl AppState {
}
fn has_launched(&self) -> bool {
!matches!(
self.state(),
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
)
!matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. })
}
fn has_terminated(&self) -> bool {
@@ -228,11 +217,9 @@ impl AppState {
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),
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 {
@@ -243,19 +230,16 @@ impl AppState {
});
}
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
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,
),
} => (queued_windows, queued_events, queued_handler, queued_gpu_redraws),
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
@@ -274,17 +258,10 @@ impl AppState {
}
let (handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => (
waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)),
),
(
ControlFlow::Wait,
AppStateImpl::Waiting {
waiting_handler,
start,
},
) => (
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => {
(waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)))
},
(ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => (
waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start,
@@ -293,10 +270,7 @@ impl AppState {
),
(
ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting {
waiting_handler,
start,
},
AppStateImpl::Waiting { waiting_handler, start },
) => {
let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
@@ -310,7 +284,7 @@ impl AppState {
}))
};
(waiting_handler, event)
}
},
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
@@ -326,18 +300,9 @@ impl AppState {
// 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,
..
} => {
&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 {
@@ -348,60 +313,49 @@ impl AppState {
queued_events,
})
};
}
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
| &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!(),
};
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,
}
UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws }
}
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
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),
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
(handler, 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 { handler, active_control_flow });
queued_gpu_redraws
}
@@ -410,10 +364,9 @@ impl AppState {
return;
}
let (waiting_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws {
handler,
active_control_flow,
} => (handler, active_control_flow),
AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
(handler, active_control_flow)
},
s => bug!("unexpected state {:?}", s),
};
@@ -421,41 +374,29 @@ 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 { waiting_handler, 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 { waiting_handler, start });
},
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting {
waiting_handler,
start,
});
self.set_state(AppStateImpl::Waiting { waiting_handler, 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 { waiting_handler, 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.waker.start()
}
},
}
}
@@ -475,54 +416,41 @@ impl AppState {
}
}
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
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::NotLaunched { ref mut queued_windows, .. } => {
return queued_windows.push(window.clone())
},
&mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
| &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: Id<WinitUIWindow>) {
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::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, .. } => {
let _ = queued_gpu_redraws.insert(window);
}
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| 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")
}
},
}
}
@@ -566,10 +494,8 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init,
)))
.chain(events);
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)))
.chain(events);
handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created.
@@ -609,7 +535,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events);
return;
}
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
@@ -630,7 +556,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
);
}
handler.handle_event(event)
}
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
@@ -638,18 +564,16 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
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),
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback {
queued_events: _,
queued_gpu_redraws,
} => queued_gpu_redraws,
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(if processing_redraws {
@@ -657,16 +581,9 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws"
);
AppStateImpl::ProcessingRedraws {
handler,
active_control_flow,
}
AppStateImpl::ProcessingRedraws { handler, active_control_flow }
} else {
AppStateImpl::ProcessingEvents {
handler,
queued_gpu_redraws,
active_control_flow,
}
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow }
});
break;
}
@@ -679,12 +596,13 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
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: {:#?}",
"processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event
);
}
handler.handle_event(event)
}
},
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
}
}
@@ -697,7 +615,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event")
}
},
UserCallbackTransitionResult::Success {
handler,
active_control_flow,
@@ -714,18 +632,16 @@ fn handle_user_events(mtm: MainThreadMarker) {
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),
&mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
mem::take(queued_events)
},
s => bug!("unexpected state {:?}", s),
};
if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback {
queued_events: _,
queued_gpu_redraws,
} => queued_gpu_redraws,
AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_gpu_redraws
},
_ => unreachable!(),
};
this.app_state = Some(AppStateImpl::ProcessingEvents {
@@ -748,13 +664,35 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
return;
}
match this.state_mut() {
AppStateImpl::ProcessingEvents { .. } => {}
AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
@@ -782,7 +720,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub fn terminated(mtm: MainThreadMarker) {
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
@@ -791,11 +749,7 @@ pub fn terminated(mtm: MainThreadMarker) {
}
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
let ScaleFactorChanged {
suggested_size,
scale_factor,
window,
} = event;
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()),
@@ -814,7 +768,7 @@ fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged)
view.setFrame(new_frame);
}
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRect) {
let view_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap();
let bounds = window.bounds();
@@ -846,7 +800,7 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
std::f64::MAX,
f64::MAX,
0.000_000_1,
0,
0,
@@ -860,11 +814,11 @@ impl EventLoopWaker {
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {
@@ -950,28 +904,21 @@ fn meets_requirements(
}
fn get_version() -> NSOperatingSystemVersion {
unsafe {
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8: bool = msg_send![
&process_info,
respondsToSelector: sel!(operatingSystemVersion)
];
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions.
// Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support
// debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7
// has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
let process_info = NSProcessInfo::processInfo();
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
// not support debugging on devices with an iOS version of less than 8. Another example, in
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
//
// The minimum required iOS version is likely to grow in the future.
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
process_info.operatingSystemVersion()
}
pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES
.get_or_init(|| OSCapabilities::from_os_version(get_version()))
.clone()
OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
}

View File

@@ -1,10 +1,8 @@
use std::{
collections::VecDeque,
ffi::c_void,
marker::PhantomData,
ptr,
sync::mpsc::{self, Receiver, Sender},
};
use std::collections::VecDeque;
use std::ffi::{c_char, c_int, c_void};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::sync::mpsc::{self, Receiver, Sender};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
@@ -13,26 +11,23 @@ use core_foundation::runloop::{
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use icrate::Foundation::{MainThreadMarker, NSString};
use objc2::ClassType;
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use crate::{
error::EventLoopError,
event::Event,
event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
},
platform::ios::Idiom,
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
window::{CustomCursor, CustomCursorSource},
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::ios::Idiom;
use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource, Theme};
use super::{app_delegate::AppDelegate, uikit::UIUserInterfaceIdiom};
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
use super::{app_state, monitor, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
};
#[derive(Debug)]
pub struct ActiveEventLoop {
@@ -42,9 +37,7 @@ pub struct ActiveEventLoop {
impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner;
CustomCursor {
inner: super::PlatformCustomCursor,
}
CustomCursor { inner: super::PlatformCustomCursor }
}
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
@@ -52,7 +45,8 @@ impl ActiveEventLoop {
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
#[allow(deprecated)]
Some(MonitorHandle::new(UIScreen::mainScreen(self.mtm)))
}
#[inline]
@@ -64,14 +58,17 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()))
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -126,7 +123,7 @@ fn map_user_event<T: 'static>(
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
}
},
}
}
@@ -151,8 +148,7 @@ impl<T: 'static> EventLoop<T> {
unsafe {
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. \
`EventLoopProxy` might be helpful"
"Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
}
@@ -166,10 +162,7 @@ impl<T: 'static> EventLoop<T> {
mtm,
sender,
receiver,
window_target: RootActiveEventLoop {
p: ActiveEventLoop { mtm },
_marker: PhantomData,
},
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
})
}
@@ -177,12 +170,13 @@ impl<T: 'static> EventLoop<T> {
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
let application = UIApplication::shared(self.mtm);
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run_app` calls `UIApplicationMain` on iOS",
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(handler, self.receiver);
@@ -194,20 +188,23 @@ impl<T: 'static> EventLoop<T> {
>(Box::new(handler))
};
let handler = EventLoopHandler {
handler,
event_loop: self.window_target,
};
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 {
UIApplicationMain(
0,
ptr::null(),
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
@@ -227,7 +224,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::current(self.mtm).userInterfaceIdiom() {
match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
@@ -282,8 +279,7 @@ impl<T> EventLoopProxy<T> {
cancel: None,
perform: event_loop_proxy_handler,
};
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
@@ -292,9 +288,7 @@ impl<T> EventLoopProxy<T> {
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
@@ -307,7 +301,8 @@ impl<T> EventLoopProxy<T> {
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other observers
// begin is queued with the highest priority to ensure it is processed before other
// observers
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
@@ -341,7 +336,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
@@ -356,7 +351,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS
kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
@@ -367,7 +362,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::min_value(),
CFIndex::MIN,
control_flow_begin_handler,
ptr::null_mut(),
);
@@ -387,7 +382,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::max_value(),
CFIndex::MAX,
control_flow_end_handler,
ptr::null_mut(),
);

View File

@@ -1,11 +1,9 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod monitor;
mod uikit;
mod view;
mod view_controller;
mod window;
@@ -14,16 +12,15 @@ use std::fmt;
use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::{
event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoModeHandle},
window::{PlatformSpecificWindowAttributes, Window, WindowId},
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
@@ -35,7 +32,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
DeviceId
}
}

View File

@@ -1,31 +1,25 @@
#![allow(clippy::unnecessary_cast)]
use std::{
collections::{BTreeSet, VecDeque},
fmt, hash, ptr,
};
use std::collections::{BTreeSet, VecDeque};
use std::{fmt, hash, ptr};
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable;
use objc2::rc::Id;
use objc2::rc::Retained;
use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::uikit::{UIScreen, UIScreenMode};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoModeHandle as RootVideoModeHandle,
platform_impl::platform::app_state,
};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::platform_impl::platform::app_state;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
fn clone(&self) -> Self {
Self(MainThreadMarker::run_on_main(|mtm| {
MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)
}))
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
}
}
@@ -33,7 +27,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)).hash(state);
Retained::as_ptr(self.0.get(mtm)).hash(state);
}
}
@@ -41,7 +35,7 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
Retained::as_ptr(self.0.get(mtm)) == Retained::as_ptr(other.0.get(mtm))
}
}
@@ -58,8 +52,8 @@ pub struct VideoModeHandle {
impl VideoModeHandle {
fn new(
uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
uiscreen: Retained<UIScreen>,
screen_mode: Retained<UIScreenMode>,
mtm: MainThreadMarker,
) -> VideoModeHandle {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
@@ -89,18 +83,18 @@ impl VideoModeHandle {
self.monitor.clone()
}
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
}
pub struct MonitorHandle {
ui_screen: MainThreadBound<Id<UIScreen>>,
ui_screen: MainThreadBound<Retained<UIScreen>>,
}
impl Clone for MonitorHandle {
fn clone(&self) -> Self {
MainThreadMarker::run_on_main(|mtm| Self {
run_on_main(|mtm| Self {
ui_screen: MainThreadBound::new(self.ui_screen.get(mtm).clone(), mtm),
})
}
@@ -108,13 +102,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)),
)
}
}
@@ -128,8 +129,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)))
}
}
@@ -146,22 +149,22 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
// Holding `Id<UIScreen>` implies we're on the main thread.
pub(crate) fn new(ui_screen: Retained<UIScreen>) -> Self {
// Holding `Retained<UIScreen>` implies we're on the main thread.
let mtm = MainThreadMarker::new().unwrap();
Self {
ui_screen: MainThreadBound::new(ui_screen, mtm),
}
Self { ui_screen: MainThreadBound::new(ui_screen, mtm) }
}
pub fn name(&self) -> Option<String> {
MainThreadMarker::run_on_main(|mtm| {
let main = UIScreen::main(mtm);
run_on_main(|mtm| {
#[allow(deprecated)]
let main = UIScreen::mainScreen(mtm);
if *self.ui_screen(mtm) == main {
Some("Primary".to_string())
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
} else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
Some("Mirrored".to_string())
} else {
#[allow(deprecated)]
UIScreen::screens(mtm)
.iter()
.position(|rhs| rhs == &**self.ui_screen(mtm))
@@ -171,33 +174,25 @@ impl MonitorHandle {
}
pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self
.ui_screen
.get_on_main(|ui_screen| ui_screen.nativeBounds());
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
}
pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self
.ui_screen
.get_on_main(|ui_screen| ui_screen.nativeBounds());
let bounds = self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into()
}
pub fn scale_factor(&self) -> f64 {
self.ui_screen
.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
self.ui_screen.get_on_main(|ui_screen| ui_screen.nativeScale()) as f64
}
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(
self.ui_screen
.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)),
)
Some(self.ui_screen.get_on_main(|ui_screen| refresh_rate_millihertz(ui_screen)))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
MainThreadMarker::run_on_main(|mtm| {
run_on_main(|mtm| {
let ui_screen = self.ui_screen(mtm);
// Use Ord impl of RootVideoModeHandle
@@ -213,12 +208,12 @@ impl MonitorHandle {
})
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoModeHandle {
MainThreadMarker::run_on_main(|mtm| {
run_on_main(|mtm| {
VideoModeHandle::new(
self.ui_screen(mtm).clone(),
self.ui_screen(mtm).preferredMode().unwrap(),
@@ -253,8 +248,30 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm)
.into_iter()
.map(MonitorHandle::new)
.collect()
#[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

@@ -1,31 +0,0 @@
use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIApplication;
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
unsafe { msg_send_id![self, windows] }
}
#[method(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View File

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

View File

@@ -1,41 +0,0 @@
use icrate::Foundation::{MainThreadMarker, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice;
unsafe impl ClassType for UIDevice {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[method(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger);
unsafe impl Encode for UIUserInterfaceIdiom {
const ENCODING: Encoding = NSInteger::ENCODING;
}
impl UIUserInterfaceIdiom {
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
}

View File

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

View File

@@ -1,14 +0,0 @@
use icrate::Foundation::NSUInteger;
use objc2::encode::{Encode, Encoding};
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(pub NSUInteger);
impl UIRectEdge {
pub const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,121 +0,0 @@
use icrate::Foundation::{CGFloat, NSInteger, NSObject, NSUInteger};
use objc2::{
encode::{Encode, Encoding},
extern_class, extern_methods, mutability, ClassType,
};
// https://developer.apple.com/documentation/uikit/uigesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIGestureRecognizer;
unsafe impl ClassType for UIGestureRecognizer {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIGestureRecognizer {
#[method(state)]
pub fn state(&self) -> UIGestureRecognizerState;
}
);
unsafe impl Encode for UIGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uigesturerecognizer/state
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIGestureRecognizerState(NSInteger);
unsafe impl Encode for UIGestureRecognizerState {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIGestureRecognizerState {
pub const Possible: Self = Self(0);
pub const Began: Self = Self(1);
pub const Changed: Self = Self(2);
pub const Ended: Self = Self(3);
pub const Cancelled: Self = Self(4);
pub const Failed: Self = Self(5);
}
// https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIPinchGestureRecognizer;
unsafe impl ClassType for UIPinchGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIPinchGestureRecognizer {
#[method(scale)]
pub fn scale(&self) -> CGFloat;
#[method(velocity)]
pub fn velocity(&self) -> CGFloat;
}
);
unsafe impl Encode for UIPinchGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIRotationGestureRecognizer;
unsafe impl ClassType for UIRotationGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIRotationGestureRecognizer {
#[method(rotation)]
pub fn rotation(&self) -> CGFloat;
#[method(velocity)]
pub fn velocity(&self) -> CGFloat;
}
);
unsafe impl Encode for UIRotationGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}
// https://developer.apple.com/documentation/uikit/uitapgesturerecognizer
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITapGestureRecognizer;
unsafe impl ClassType for UITapGestureRecognizer {
type Super = UIGestureRecognizer;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITapGestureRecognizer {
#[method(setNumberOfTapsRequired:)]
pub fn setNumberOfTapsRequired(&self, number_of_taps_required: NSUInteger);
#[method(setNumberOfTouchesRequired:)]
pub fn setNumberOfTouchesRequired(&self, number_of_touches_required: NSUInteger);
}
);
unsafe impl Encode for UITapGestureRecognizer {
const ENCODING: Encoding = Encoding::Object;
}

View File

@@ -1,52 +0,0 @@
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use std::os::raw::{c_char, c_int};
use icrate::Foundation::NSString;
mod application;
mod coordinate_space;
mod device;
mod event;
mod geometry;
mod gesture_recognizer;
mod responder;
mod screen;
mod screen_mode;
mod status_bar_style;
mod touch;
mod trait_collection;
mod view;
mod view_controller;
mod window;
pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
pub(crate) use self::event::UIEvent;
pub(crate) use self::geometry::UIRectEdge;
pub(crate) use self::gesture_recognizer::{
UIGestureRecognizer, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIRotationGestureRecognizer, UITapGestureRecognizer,
};
pub(crate) use self::responder::UIResponder;
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::status_bar_style::UIStatusBarStyle;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]
pub(crate) use self::view::{UIEdgeInsets, UIView};
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
pub(crate) use self::window::UIWindow;
#[link(name = "UIKit", kind = "framework")]
extern "C" {
pub fn UIApplicationMain(
argc: c_int,
argv: *const c_char,
principalClassName: Option<&NSString>,
delegateClassName: Option<&NSString>,
) -> c_int;
}

View File

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

View File

@@ -1,80 +0,0 @@
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreen;
unsafe impl ClassType for UIScreen {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[method(scale)]
pub fn scale(&self) -> CGFloat;
#[method(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[method(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[method(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[method(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
unsafe { msg_send_id![self, availableModes] }
}
#[method(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: Self = Self(0);
pub const InsetBounds: Self = Self(1);
pub const None: Self = Self(2);
}

View File

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

View File

@@ -1,16 +0,0 @@
use icrate::Foundation::NSInteger;
use objc2::encode::{Encode, Encoding};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIStatusBarStyle {
#[default]
Default = 0,
LightContent = 1,
DarkContent = 3,
}
unsafe impl Encode for UIStatusBarStyle {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,65 +0,0 @@
use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, extern_methods, mutability, ClassType};
use super::UIView;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITouch;
unsafe impl ClassType for UITouch {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITouch {
#[method(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[method(type)]
pub fn type_(&self) -> UITouchType;
#[method(force)]
pub fn force(&self) -> CGFloat;
#[method(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[method(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[method(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
unsafe impl Encode for UITouchType {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
unsafe impl Encode for UITouchPhase {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,33 +0,0 @@
use icrate::Foundation::{NSInteger, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, extern_methods, mutability, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITraitCollection;
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[method(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
unsafe impl Encode for UIForceTouchCapability {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View File

@@ -1,96 +0,0 @@
use icrate::Foundation::{CGFloat, CGRect, NSObject};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIView;
unsafe impl ClassType for UIView {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIView {
#[method(bounds)]
pub fn bounds(&self) -> CGRect;
#[method(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[method(frame)]
pub fn frame(&self) -> CGRect;
#[method(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[method(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[method(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[method(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[method(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[method(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[method(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[method(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[method(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
#[method(addGestureRecognizer:)]
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
#[method(removeGestureRecognizer:)]
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
}
);
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
unsafe impl Encode for UIEdgeInsets {
const ENCODING: Encoding = Encoding::Struct(
"UIEdgeInsets",
&[
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
],
);
}

View File

@@ -1,57 +0,0 @@
use icrate::Foundation::{NSObject, NSUInteger};
use objc2::encode::{Encode, Encoding};
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIViewController;
unsafe impl ClassType for UIViewController {
#[inherits(NSObject)]
type Super = UIResponder;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIViewController {
#[method(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[method(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
pub fn view(&self) -> Option<Id<UIView>> {
unsafe { msg_send_id![self, view] }
}
#[method(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);
bitflags::bitflags! {
#[derive(Clone, Copy)]
pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1;
const PortraitUpsideDown = 1 << 2;
const LandscapeRight = 1 << 3;
const LandscapeLeft = 1 << 4;
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
}
}
unsafe impl Encode for UIInterfaceOrientationMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View File

@@ -1,36 +0,0 @@
use icrate::Foundation::NSObject;
use objc2::rc::Id;
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
use super::{UIResponder, UIScreen, UIView};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIWindow;
unsafe impl ClassType for UIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen> {
unsafe { msg_send_id![self, screen] }
}
#[method(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[method(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[method(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
#[method(isKeyWindow)]
pub fn isKeyWindow(&self) -> bool;
}
);

View File

@@ -1,31 +1,36 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::rc::Id;
use objc2::runtime::AnyClass;
use objc2::{
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
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, NSString};
use objc2_ui_kit::{
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use super::app_state::{self, EventWrapper};
use super::uikit::{
UIEvent, UIForceTouchCapability, UIGestureRecognizerState, UIPinchGestureRecognizer,
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase,
UITouchType, UITraitCollection, UIView,
};
use super::window::WinitUIWindow;
use crate::{
dpi::PhysicalPosition,
event::{Event, Force, Touch, TouchPhase, WindowEvent},
platform_impl::platform::DEVICE_ID,
window::{WindowAttributes, WindowId as RootWindowId},
};
use crate::dpi::PhysicalPosition;
use crate::event::{ElementState, Event, Force, KeyEvent, Touch, TouchPhase, WindowEvent};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::platform_impl::platform::DEVICE_ID;
use crate::platform_impl::KeyEventExtra;
use crate::window::{WindowAttributes, WindowId as RootWindowId};
pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
pinch_gesture_recognizer: RefCell<Option<Retained<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Retained<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Retained<UIRotationGestureRecognizer>>>,
pan_gesture_recognizer: RefCell<Option<Retained<UIPanGestureRecognizer>>>,
// for iOS delta references the start of the Gesture
rotation_last_delta: Cell<CGFloat>,
pinch_last_delta: Cell<CGFloat>,
pan_last_delta: Cell<CGPoint>,
}
declare_class!(
@@ -34,7 +39,7 @@ declare_class!(
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIView";
}
@@ -167,12 +172,23 @@ declare_class!(
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
let (phase, delta) = match recognizer.state() {
UIGestureRecognizerState::Began => {
self.ivars().pinch_last_delta.set(recognizer.scale());
(TouchPhase::Started, 0.0)
}
UIGestureRecognizerState::Changed => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(recognizer.scale());
(TouchPhase::Moved, recognizer.scale() - last_scale)
}
UIGestureRecognizerState::Ended => {
let last_scale: f64 = self.ivars().pinch_last_delta.replace(0.0);
(TouchPhase::Moved, recognizer.scale() - last_scale)
}
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale())
}
state => panic!("unexpected recognizer state: {:?}", state),
};
@@ -181,7 +197,7 @@ declare_class!(
window_id: RootWindowId(window.id()),
event: WindowEvent::PinchGesture {
device_id: DEVICE_ID,
delta: recognizer.velocity() as _,
delta: delta as f64,
phase,
},
});
@@ -211,23 +227,37 @@ declare_class!(
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap();
let phase = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started,
UIGestureRecognizerState::Changed => TouchPhase::Moved,
UIGestureRecognizerState::Ended => TouchPhase::Ended,
let (phase, delta) = match recognizer.state() {
UIGestureRecognizerState::Began => {
self.ivars().rotation_last_delta.set(0.0);
(TouchPhase::Started, 0.0)
}
UIGestureRecognizerState::Changed => {
let last_rotation = self.ivars().rotation_last_delta.replace(recognizer.rotation());
(TouchPhase::Moved, recognizer.rotation() - last_rotation)
}
UIGestureRecognizerState::Ended => {
let last_rotation = self.ivars().rotation_last_delta.replace(0.0);
(TouchPhase::Ended, recognizer.rotation() - last_rotation)
}
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
TouchPhase::Cancelled
self.ivars().rotation_last_delta.set(0.0);
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation())
}
state => panic!("unexpected recognizer state: {:?}", state),
};
// Flip the velocity to match macOS.
let delta = -recognizer.velocity() as _;
// Make delta negative to match macos, convert to degrees
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::RotationGesture {
device_id: DEVICE_ID,
delta,
delta: -delta.to_degrees() as _,
phase,
},
});
@@ -235,38 +265,111 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(panGesture:)]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let window = self.window().unwrap();
let translation = recognizer.translationInView(Some(self));
let (phase, dx, dy) = match recognizer.state() {
UIGestureRecognizerState::Began => {
self.ivars().pan_last_delta.set(translation);
(TouchPhase::Started, 0.0, 0.0)
}
UIGestureRecognizerState::Changed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(translation);
let dx = translation.x - last_pan.x;
let dy = translation.y - last_pan.y;
(TouchPhase::Moved, dx, dy)
}
UIGestureRecognizerState::Ended => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
let dx = translation.x - last_pan.x;
let dy = translation.y - last_pan.y;
(TouchPhase::Ended, dx, dy)
}
UIGestureRecognizerState::Cancelled | UIGestureRecognizerState::Failed => {
let last_pan: CGPoint = self.ivars().pan_last_delta.replace(CGPoint{x:0.0, y:0.0});
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}
state => panic!("unexpected recognizer state: {:?}", state),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::PanGesture {
device_id: DEVICE_ID,
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
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitView {
fn window(&self) -> Option<Id<WinitUIWindow>> {
unsafe { msg_send_id![self, window] }
unsafe impl NSObjectProtocol for WinitView {}
unsafe impl UIGestureRecognizerDelegate for WinitView {
#[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
true
}
}
unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UIKeyInput for WinitView {
#[method(hasText)]
fn has_text(&self) -> bool {
true
}
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
msg_send_id![self, traitCollection]
#[method(insertText:)]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
// TODO: Allow the user to customize this
#[method(layerClass)]
pub(crate) fn layerClass() -> &'static AnyClass;
#[method(deleteBackward)]
fn delete_backward(&self) {
self.handle_delete_backward()
}
}
);
impl WinitView {
pub(crate) fn new(
_mtm: MainThreadMarker,
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
frame: CGRect,
) -> Id<Self> {
let this = Self::alloc().set_ivars(WinitViewState {
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(WinitViewState {
pinch_gesture_recognizer: RefCell::new(None),
doubletap_gesture_recognizer: RefCell::new(None),
rotation_gesture_recognizer: RefCell::new(None),
pan_gesture_recognizer: RefCell::new(None),
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 }),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
@@ -277,12 +380,23 @@ impl WinitView {
this
}
fn window(&self) -> Option<Retained<WinitUIWindow>> {
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
(**self).window().map(|window| unsafe { Retained::cast(window) })
}
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize {
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
let pinch = unsafe {
UIPinchGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(pinchGesture:)),
)
};
pinch.setDelegate(Some(ProtocolObject::from_ref(self)));
self.addGestureRecognizer(&pinch);
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
}
@@ -291,12 +405,45 @@ impl WinitView {
}
}
pub(crate) fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
let mtm = MainThreadMarker::from(self);
if should_recognize {
if self.ivars().pan_gesture_recognizer.borrow().is_none() {
let pan = unsafe {
UIPanGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(panGesture:)),
)
};
pan.setDelegate(Some(ProtocolObject::from_ref(self)));
pan.setMinimumNumberOfTouches(minimum_number_of_touches as _);
pan.setMaximumNumberOfTouches(maximum_number_of_touches as _);
self.addGestureRecognizer(&pan);
self.ivars().pan_gesture_recognizer.replace(Some(pan));
}
} else if let Some(recognizer) = self.ivars().pan_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
}
}
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize {
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
let tap: Id<UITapGestureRecognizer> = unsafe {
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
let tap = unsafe {
UITapGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(doubleTapGesture:)),
)
};
tap.setDelegate(Some(ProtocolObject::from_ref(self)));
tap.setNumberOfTapsRequired(2);
tap.setNumberOfTouchesRequired(1);
self.addGestureRecognizer(&tap);
@@ -308,15 +455,19 @@ impl WinitView {
}
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize {
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
let rotation = unsafe {
UIRotationGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(rotationGesture:)),
)
};
rotation.setDelegate(Some(ProtocolObject::from_ref(self)));
self.addGestureRecognizer(&rotation);
self.ivars()
.rotation_gesture_recognizer
.replace(Some(rotation));
self.ivars().rotation_gesture_recognizer.replace(Some(rotation));
}
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer);
@@ -329,9 +480,9 @@ impl WinitView {
let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches {
let logical_location = touch.locationInView(None);
let touch_type = touch.type_();
let touch_type = touch.r#type();
let force = if os_supports_force {
let trait_collection = unsafe { self.traitCollection() };
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
@@ -364,7 +515,7 @@ impl WinitView {
// 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32),
_ => panic!("unexpected touch phase: {phase:?}"),
};
let physical_location = {
@@ -388,4 +539,69 @@ impl WinitView {
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 = RootWindowId(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::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
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,
device_id: DEVICE_ID,
},
})
})
}),
);
}
fn handle_delete_backward(&self) {
let window = self.window().unwrap();
let window_id = RootWindowId(window.id());
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
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

@@ -1,16 +1,16 @@
use std::cell::Cell;
use icrate::Foundation::{MainThreadMarker, NSObject};
use objc2::rc::Id;
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use super::app_state::{self};
use super::uikit::{
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2_ui_kit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController,
};
use crate::platform::ios::{ScreenEdge, StatusBarStyle};
use crate::{platform::ios::ValidOrientations, window::WindowAttributes};
use super::app_state::{self};
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::window::WindowAttributes;
pub struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
@@ -26,7 +26,7 @@ declare_class!(
unsafe impl ClassType for WinitViewController {
#[inherits(UIResponder, NSObject)]
type Super = UIViewController;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIViewController";
}
@@ -97,16 +97,10 @@ impl WinitViewController {
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: ScreenEdge) {
let val = {
assert_eq!(
val.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
assert_eq!(val.bits() & !ScreenEdge::ALL.bits(), 0, "invalid `ScreenEdge`");
UIRectEdge(val.bits().into())
};
self.ivars()
.preferred_screen_edges_deferring_system_gestures
.set(val);
self.ivars().preferred_screen_edges_deferring_system_gestures.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
@@ -120,52 +114,46 @@ impl WinitViewController {
mtm: MainThreadMarker,
valid_orientations: ValidOrientations,
) {
let mask = match (
valid_orientations,
UIDevice::current(mtm).userInterfaceIdiom(),
) {
let mask = match (valid_orientations, UIDevice::currentDevice(mtm).userInterfaceIdiom()) {
(ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
},
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::Portrait
}
},
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
},
};
self.ivars().supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
#[allow(deprecated)]
UIViewController::attemptRotationToDeviceOrientation(mtm);
}
pub(crate) fn new(
mtm: MainThreadMarker,
window_attributes: &WindowAttributes,
view: &UIView,
) -> Id<Self> {
) -> Retained<Self> {
// These are set properly below, we just to set them to something in the meantime.
let this = Self::alloc().set_ivars(ViewControllerState {
let this = mtm.alloc().set_ivars(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.set_prefers_status_bar_hidden(
window_attributes
.platform_specific
.prefers_status_bar_hidden,
window_attributes.platform_specific.prefers_status_bar_hidden,
);
this.set_preferred_status_bar_style(
window_attributes
.platform_specific
.preferred_status_bar_style,
window_attributes.platform_specific.preferred_status_bar_style,
);
this.set_supported_interface_orientations(
@@ -174,15 +162,11 @@ impl WinitViewController {
);
this.set_prefers_home_indicator_auto_hidden(
window_attributes
.platform_specific
.prefers_home_indicator_hidden,
window_attributes.platform_specific.prefers_home_indicator_hidden,
);
this.set_preferred_screen_edges_deferring_system_gestures(
window_attributes
.platform_specific
.preferred_screen_edges_deferring_system_gestures,
window_attributes.platform_specific.preferred_screen_edges_deferring_system_gestures,
);
this.setView(Some(view));

View File

@@ -2,30 +2,33 @@
use std::collections::VecDeque;
use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
use objc2::rc::Id;
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,
};
use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
UIViewController, UIWindow,
};
use tracing::{debug, warn};
use super::app_state::EventWrapper;
use super::uikit::{
UIApplication, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use super::view::WinitView;
use super::view_controller::WinitViewController;
use crate::{
cursor::Cursor,
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
event::{Event, WindowEvent},
icon::Icon,
platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations},
platform_impl::platform::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle},
window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
},
use crate::cursor::Cursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::platform_impl::platform::{
app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle,
};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
};
declare_class!(
@@ -35,7 +38,7 @@ declare_class!(
unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)]
type Super = UIWindow;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIWindow";
}
@@ -76,8 +79,8 @@ impl WinitUIWindow {
window_attributes: &WindowAttributes,
frame: CGRect,
view_controller: &UIViewController,
) -> Id<Self> {
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
) -> Retained<Self> {
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
this.setRootViewController(Some(view_controller));
@@ -87,11 +90,11 @@ impl WinitUIWindow {
let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen);
}
},
Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen(mtm);
this.setScreen(screen);
}
},
_ => (),
}
@@ -104,9 +107,9 @@ impl WinitUIWindow {
}
pub struct Inner {
window: Id<WinitUIWindow>,
view_controller: Id<WinitViewController>,
view: Id<WinitView>,
window: Retained<WinitUIWindow>,
view_controller: Retained<WinitViewController>,
view: Retained<WinitView>,
gl_or_metal_backed: bool,
}
@@ -135,12 +138,13 @@ impl Inner {
pub fn request_redraw(&self) {
if self.gl_or_metal_backed {
let mtm = MainThreadMarker::new().unwrap();
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
// raw or gl/metal for drawing this work is completely avoided.
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or
// CAMetalLayer. Ordinarily the OS sets up a bunch of UIKit state before
// calling drawRect: on a UIView, but when using raw or gl/metal for drawing
// this work is completely avoided.
//
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing.
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been
// confirmed via testing.
//
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
@@ -153,20 +157,16 @@ impl Inner {
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let safe_area = self.safe_area_screen_space();
let position = LogicalPosition {
x: safe_area.origin.x as f64,
y: safe_area.origin.y as f64,
};
let position =
LogicalPosition { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64 };
let scale_factor = self.scale_factor();
Ok(position.to_physical(scale_factor))
}
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
let screen_frame = self.screen_frame();
let position = LogicalPosition {
x: screen_frame.origin.x as f64,
y: screen_frame.origin.y as f64,
};
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))
}
@@ -176,10 +176,7 @@ impl Inner {
let position = physical_position.to_logical::<f64>(scale_factor);
let screen_frame = self.screen_frame();
let new_screen_frame = CGRect {
origin: CGPoint {
x: position.x as _,
y: position.y as _,
},
origin: CGPoint { x: position.x as _, y: position.y as _ },
size: screen_frame.size,
};
let bounds = self.rect_from_screen_space(new_screen_frame);
@@ -307,15 +304,15 @@ impl Inner {
let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
uiscreen.clone()
}
},
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen(mtm).clone()
}
},
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
},
};
// this is pretty slow on iOS, so avoid doing it if we can
@@ -370,12 +367,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) {
@@ -400,9 +409,8 @@ impl Inner {
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(
MainThreadMarker::new().unwrap(),
)))
#[allow(deprecated)]
Some(MonitorHandle::new(UIScreen::mainScreen(MainThreadMarker::new().unwrap())))
}
pub fn id(&self) -> WindowId {
@@ -412,18 +420,18 @@ impl Inner {
#[cfg(feature = "rwh_04")]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::UiKitHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
window_handle.ui_window = Retained::as_ptr(&self.window) as _;
window_handle.ui_view = Retained::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _;
rwh_04::RawWindowHandle::UiKit(window_handle)
}
#[cfg(feature = "rwh_05")]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
window_handle.ui_window = Retained::as_ptr(&self.window) as _;
window_handle.ui_view = Retained::as_ptr(&self.view) as _;
window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _;
rwh_05::RawWindowHandle::UiKit(window_handle)
}
@@ -435,11 +443,11 @@ impl Inner {
#[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 = Id::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
let ui_view = Retained::as_ptr(&self.view) as _;
std::ptr::NonNull::new(ui_view).expect("Retained<T> should never be null")
});
window_handle.ui_view_controller =
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
std::ptr::NonNull::new(Retained::as_ptr(&self.view_controller) as _);
rwh_06::RawWindowHandle::UiKit(window_handle)
}
@@ -489,7 +497,8 @@ impl Window {
// TODO: transparency, visible
let main_screen = UIScreen::main(mtm);
#[allow(deprecated)]
let main_screen = UIScreen::mainScreen(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
@@ -505,23 +514,16 @@ impl Window {
let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect {
origin: screen_bounds.origin,
size: CGSize {
width: size.width as _,
height: size.height as _,
},
size: CGSize { width: size.width as _, height: size.height as _ },
}
}
},
None => screen_bounds,
};
let view = WinitView::new(mtm, &window_attributes, frame);
let gl_or_metal_backed = unsafe {
let layer_class = WinitView::layerClass();
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal || is_gl
};
let gl_or_metal_backed =
view.isKindOfClass(class!(CAMetalLayer)) || view.isKindOfClass(class!(CAEAGLLayer));
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
@@ -544,13 +546,11 @@ impl Window {
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),
},
))
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,
@@ -560,15 +560,8 @@ impl Window {
);
}
let inner = Inner {
window,
view_controller,
view,
gl_or_metal_backed,
};
Ok(Window {
inner: MainThreadBound::new(inner, mtm),
})
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) {
@@ -597,9 +590,7 @@ impl Window {
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit(
rwh_06::UiKitDisplayHandle::new(),
))
Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()))
}
}
@@ -622,13 +613,11 @@ impl Inner {
}
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller
.set_prefers_home_indicator_auto_hidden(hidden);
self.view_controller.set_prefers_home_indicator_auto_hidden(hidden);
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.view_controller
.set_preferred_screen_edges_deferring_system_gestures(edges);
self.view_controller.set_preferred_screen_edges_deferring_system_gestures(edges);
}
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
@@ -636,14 +625,26 @@ impl Inner {
}
pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.view_controller
.set_preferred_status_bar_style(status_bar_style);
self.view_controller.set_preferred_status_bar_style(status_bar_style);
}
pub fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.view.recognize_pinch_gesture(should_recognize);
}
pub fn recognize_pan_gesture(
&self,
should_recognize: bool,
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
self.view.recognize_pan_gesture(
should_recognize,
minimum_number_of_touches,
maximum_number_of_touches,
);
}
pub fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.view.recognize_doubletap_gesture(should_recognize);
}
@@ -660,14 +661,12 @@ impl Inner {
fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_toCoordinateSpace(rect, &screen_space)
self.window.convertRect_toCoordinateSpace(rect, &screen_space)
}
fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen_space = self.window.screen().coordinateSpace();
self.window
.convertRect_fromCoordinateSpace(rect, &screen_space)
self.window.convertRect_fromCoordinateSpace(rect, &screen_space)
}
fn safe_area_screen_space(&self) -> CGRect {
@@ -688,9 +687,8 @@ impl Inner {
} else {
let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
"`Window::get_inner_position` cannot be called before `EventLoop::run_app` on iOS",
);
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
#[allow(deprecated)]
app.statusBarFrame()
};
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
@@ -702,14 +700,8 @@ impl Inner {
(y, height)
};
CGRect {
origin: CGPoint {
x: screen_frame.origin.x,
y,
},
size: CGSize {
width: screen_frame.size.width,
height,
},
origin: CGPoint { x: screen_frame.origin.x, y },
size: CGSize { width: screen_frame.size.width, height },
}
}
}
@@ -721,10 +713,8 @@ pub struct WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
WindowId {
window: std::ptr::null_mut(),
}
pub const fn dummy() -> Self {
WindowId { window: std::ptr::null_mut() }
}
}
@@ -736,9 +726,7 @@ impl From<WindowId> for u64 {
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self {
window: raw_id as _,
}
Self { window: raw_id as _ }
}
}
@@ -747,9 +735,7 @@ unsafe impl Sync for WindowId {}
impl From<&AnyObject> for WindowId {
fn from(window: &AnyObject) -> WindowId {
WindowId {
window: window as *const _ as _,
}
WindowId { window: window as *const _ as _ }
}
}

View File

@@ -6,8 +6,7 @@ use std::ops::Deref;
use std::os::unix::ffi::OsStringExt;
use std::ptr::NonNull;
use super::XkbContext;
use super::XKBCH;
use super::{XkbContext, XKBCH};
use smol_str::SmolStr;
use xkbcommon_dl::{
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
@@ -91,7 +90,7 @@ impl XkbComposeState {
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
ComposeStatus::Accepted(self.status())
}
},
}
}

View File

@@ -296,7 +296,7 @@ pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option<u32> {
NativeKeyCode::Xkb(raw) => Some(raw),
_ => None,
};
}
},
};
match code {
@@ -627,7 +627,6 @@ pub fn keysym_to_key(keysym: u32) -> Key {
// keysyms::ISO_First_Group_Lock => NamedKey::GroupFirstLock,
keysyms::ISO_Last_Group => NamedKey::GroupLast,
// keysyms::ISO_Last_Group_Lock => NamedKey::GroupLastLock,
//
keysyms::ISO_Left_Tab => NamedKey::Tab,
// keysyms::ISO_Move_Line_Up => NamedKey::IsoMoveLineUp,
// keysyms::ISO_Move_Line_Down => NamedKey::IsoMoveLineDown,
@@ -809,18 +808,15 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::XF86_Music => NamedKey::LaunchMusicPlayer,
// XF86_Battery..XF86_UWB
//
keysyms::XF86_AudioForward => NamedKey::MediaFastForward,
// XF86_AudioRepeat
keysyms::XF86_AudioRandomPlay => NamedKey::RandomToggle,
keysyms::XF86_Subtitle => NamedKey::Subtitle,
keysyms::XF86_AudioCycleTrack => NamedKey::MediaAudioTrack,
// XF86_CycleAngle..XF86_Blue
//
keysyms::XF86_Suspend => NamedKey::Standby,
keysyms::XF86_Hibernate => NamedKey::Hibernate,
// XF86_TouchpadToggle..XF86_TouchpadOff
//
keysyms::XF86_AudioMute => NamedKey::AudioVolumeMute,
// XF86_Switch_VT_1..XF86_Switch_VT_12
@@ -853,7 +849,6 @@ pub fn keysym_to_key(keysym: u32) -> Key {
keysyms::SUN_VideoLowerBrightness => NamedKey::BrightnessDown,
keysyms::SUN_VideoRaiseBrightness => NamedKey::BrightnessUp,
// SunPowerSwitchShift
//
0 => return Key::Unidentified(NativeKey::Unidentified),
_ => return Key::Unidentified(NativeKey::Xkb(keysym)),
})
@@ -968,11 +963,7 @@ impl XkbKeymap {
mod5: mod_index_for_name(keymap, b"Mod5\0"),
};
Self {
keymap,
_mods_indices: mods_indices,
_core_keyboard_id,
}
Self { keymap, _mods_indices: mods_indices, _core_keyboard_id }
}
#[cfg(x11_platform)]
@@ -1020,12 +1011,14 @@ impl Drop for XkbKeymap {
impl Deref for XkbKeymap {
type Target = NonNull<xkb_keymap>;
fn deref(&self) -> &Self::Target {
&self.keymap
}
}
/// Modifier index in the keymap.
#[cfg_attr(not(x11_platform), allow(dead_code))]
#[derive(Default, Debug, Clone, Copy)]
pub struct ModsIndices {
pub shift: Option<xkb_mod_index_t>,

View File

@@ -15,8 +15,7 @@ use xkbcommon_dl::{
#[cfg(x11_platform)]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::ElementState;
use crate::event::KeyEvent;
use crate::event::{ElementState, KeyEvent};
use crate::keyboard::{Key, KeyLocation};
use crate::platform_impl::KeyEventExtra;
@@ -143,9 +142,7 @@ impl Context {
#[cfg(x11_platform)]
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
let state = keymap
.as_ref()
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap));
if keymap.is_none() || state.is_none() {
warn!("failed to update xkb keymap");
}
@@ -160,13 +157,7 @@ impl Context {
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
}
/// Key builder context with the user provided xkb state.
@@ -181,13 +172,7 @@ impl Context {
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext {
state,
keymap,
compose_state1,
compose_state2,
scratch_buffer,
})
Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
}
}
@@ -199,7 +184,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,
@@ -214,20 +199,9 @@ impl<'a> KeyContext<'a> {
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
let platform_specific = KeyEventExtra {
text_with_all_modifiers,
key_without_modifiers,
};
let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers };
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
platform_specific,
}
KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific }
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
@@ -246,10 +220,7 @@ impl<'a> KeyContext<'a> {
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe {
self.scratch_buffer
.set_len(bytes_written.try_into().unwrap())
};
unsafe { self.scratch_buffer.set_len(bytes_written.try_into().unwrap()) };
break;
}
}
@@ -281,12 +252,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
ComposeStatus::None
};
KeyEventResults {
context,
keycode,
keysym,
compose,
}
KeyEventResults { context, keycode, keysym, compose }
}
pub fn key(&mut self) -> (Key, KeyLocation) {
@@ -323,23 +289,18 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
// This will become a pointer to an array which libxkbcommon owns, so we don't need to
// deallocate it.
let layout = self.context.state.layout(self.keycode);
let keysym = self
.context
.keymap
.first_keysym_by_level(layout, self.keycode);
let keysym = self.context.keymap.first_keysym_by_level(layout, self.keycode);
match self.keysym_to_key(keysym) {
Ok((key, location)) => (key, location),
Err((key, location)) => {
let key = self
.context
.keysym_to_utf8_raw(keysym)
.map(Key::Character)
.unwrap_or(key);
let key =
self.context.keysym_to_utf8_raw(keysym).map(Key::Character).unwrap_or(key);
(key, location)
}
},
}
}
@@ -354,8 +315,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
self.composed_text().unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
}
// The current behaviour makes it so composing a character overrides attempts to input a
@@ -364,10 +324,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,
Err(_) => self
.context
.state
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
Err(_) => self.context.state.get_utf8_raw(self.keycode, self.context.scratch_buffer),
}
}
@@ -377,7 +334,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
xkb_compose_status::XKB_COMPOSE_COMPOSED => {
let state = self.context.compose_state1.as_mut().unwrap();
Ok(state.get_string(self.context.scratch_buffer))
}
},
xkb_compose_status::XKB_COMPOSE_COMPOSING
| xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
@@ -436,10 +393,7 @@ where
// The allocated buffer must include space for the null-terminator.
scratch_buffer.reserve(size + 1);
unsafe {
let written = f(
scratch_buffer.as_mut_ptr().cast(),
scratch_buffer.capacity(),
);
let written = f(scratch_buffer.as_mut_ptr().cast(), scratch_buffer.capacity());
if usize::try_from(written).unwrap() != size {
// This will likely never happen.
return None;
@@ -456,10 +410,7 @@ fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
tracing::warn!(
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
bytes
)
tracing::warn!("UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes)
})
.ok()
}

View File

@@ -3,10 +3,11 @@
#[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::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::Arc;
use std::time::Duration;
use std::{collections::VecDeque, env, fmt};
use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
@@ -16,22 +17,19 @@ use smol_str::SmolStr;
#[cfg(x11_platform)]
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::icon::Icon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::window::{CustomCursor, CustomCursorSource};
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError},
event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
},
icon::Icon,
keyboard::Key,
platform::pump_events::PumpStatus,
window::{
ActivationToken, Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
},
use crate::window::{
ActivationToken, Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose,
ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
};
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
@@ -92,6 +90,7 @@ pub struct X11WindowAttributes {
pub embed_window: Option<x11rb::protocol::xproto::Window>,
}
#[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))]
impl Default for PlatformSpecificWindowAttributes {
fn default() -> Self {
Self {
@@ -118,6 +117,8 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XNotSupported(XNotSupported),
#[cfg(x11_platform)]
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandError(Arc<wayland::WaylandError>),
@@ -128,6 +129,8 @@ impl fmt::Display for OsError {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XNotSupported(ref e) => fmt::Display::fmt(e, _f),
#[cfg(x11_platform)]
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
@@ -158,7 +161,7 @@ impl From<u64> for WindowId {
}
impl WindowId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
Self(0)
}
}
@@ -172,11 +175,11 @@ pub enum DeviceId {
}
impl DeviceId {
pub const unsafe fn dummy() -> Self {
pub const fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
return DeviceId::Wayland(wayland::DeviceId::dummy());
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
return DeviceId::X(x11::DeviceId::dummy());
}
}
@@ -293,11 +296,11 @@ impl Window {
#[cfg(wayland_platform)]
ActiveEventLoop::Wayland(ref window_target) => {
wayland::Window::new(window_target, attribs).map(Window::Wayland)
}
},
#[cfg(x11_platform)]
ActiveEventLoop::X(ref window_target) => {
x11::Window::new(window_target, attribs).map(Window::X)
}
},
}
}
@@ -533,6 +536,7 @@ impl Window {
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))
}
@@ -556,17 +560,13 @@ impl Window {
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(),
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(),
Window::Wayland(ref window) => {
window.available_monitors().into_iter().map(MonitorHandle::Wayland).collect()
},
}
}
@@ -647,18 +647,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 _);
}
@@ -725,7 +725,8 @@ impl<T: 'static> EventLoop<T> {
"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 \
`EventLoopBuilderExtUnix::any_thread` function."
`EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
functions."
);
}
@@ -739,12 +740,10 @@ impl<T: 'static> EventLoop<T> {
.or_else(|| env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some(),
env::var("DISPLAY")
.map(|var| !var.is_empty())
.unwrap_or(false),
env::var("DISPLAY").map(|var| !var.is_empty()).unwrap_or(false),
) {
// User is forcing a backend.
(Some(backend), _, _) => backend,
(Some(backend), ..) => backend,
// Wayland is present.
#[cfg(wayland_platform)]
(None, true, _) => Backend::Wayland,
@@ -754,14 +753,16 @@ impl<T: 'static> EventLoop<T> {
// No backend is present.
(_, wayland_display, x11_display) => {
let msg = if wayland_display && !cfg!(wayland_platform) {
"DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland"
"DISPLAY is not set; note: enable the `winit/wayland` feature to support \
Wayland"
} else if x11_display && !cfg!(x11_platform) {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11"
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the \
`winit/x11` feature to support X11"
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(EventLoopError::Os(os_error!(OsError::Misc(msg))));
}
},
};
// Create the display based on the backend.
@@ -780,14 +781,26 @@ impl<T: 'static> EventLoop<T> {
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, 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(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone()))))
},
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
}
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(wayland_platform)]
EventLoop::Wayland(_) => true,
#[cfg(x11_platform)]
_ => false,
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
@@ -862,14 +875,13 @@ impl ActiveEventLoop {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
match *self {
#[cfg(wayland_platform)]
ActiveEventLoop::Wayland(ref evlp) => evlp
.available_monitors()
.map(MonitorHandle::Wayland)
.collect(),
ActiveEventLoop::Wayland(ref evlp) => {
evlp.available_monitors().map(MonitorHandle::Wayland).collect()
},
#[cfg(x11_platform)]
ActiveEventLoop::X(ref evlp) => {
evlp.available_monitors().map(MonitorHandle::X).collect()
}
},
}
}
@@ -891,6 +903,11 @@ impl ActiveEventLoop {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -959,7 +976,7 @@ impl OwnedDisplayHandle {
xlib_handle.display = xconn.display.cast();
xlib_handle.screen = xconn.default_screen_index() as _;
xlib_handle.into()
}
},
#[cfg(wayland_platform)]
Self::Wayland(conn) => {
@@ -968,7 +985,7 @@ impl OwnedDisplayHandle {
let mut wayland_handle = rwh_05::WaylandDisplayHandle::empty();
wayland_handle.display = conn.display().id().as_ptr() as *mut _;
wayland_handle.into()
}
},
}
}
@@ -995,7 +1012,7 @@ impl OwnedDisplayHandle {
NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
)
.into())
}
},
}
}
}
@@ -1004,9 +1021,7 @@ impl OwnedDisplayHandle {
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| {
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
#[cfg(target_os = "linux")]

View File

@@ -12,8 +12,7 @@ use std::time::{Duration, Instant};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, QueueHandle};
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
@@ -81,26 +80,19 @@ impl<T: 'static> EventLoop<T> {
let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?;
let (globals, mut event_queue) = map_err!(
globals::registry_queue_init(&connection),
WaylandError::Global
)?;
let (globals, mut event_queue) =
map_err!(globals::registry_queue_init(&connection), WaylandError::Global)?;
let queue_handle = event_queue.handle();
let event_loop = map_err!(
calloop::EventLoop::<WinitState>::try_new(),
WaylandError::Calloop
)?;
let event_loop =
map_err!(calloop::EventLoop::<WinitState>::try_new(), WaylandError::Calloop)?;
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())
.map_err(|error| os_error!(error))?;
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
map_err!(
event_queue.roundtrip(&mut winit_state),
WaylandError::Dispatch
)?;
map_err!(event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch)?;
// Register Wayland source.
let wayland_source = WaylandSource::new(connection.clone(), event_queue);
@@ -117,9 +109,7 @@ impl<T: 'static> EventLoop<T> {
});
map_err!(
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone()),
event_loop.handle().register_dispatcher(wayland_dispatcher.clone()),
WaylandError::Calloop
)?;
@@ -129,15 +119,12 @@ impl<T: 'static> EventLoop<T> {
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let result = event_loop
.handle()
.insert_source(
user_events_channel,
move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
},
)
.insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
@@ -150,13 +137,10 @@ impl<T: 'static> EventLoop<T> {
let result = 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;
},
)
.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)?;
@@ -197,13 +181,13 @@ impl<T: 'static> EventLoop<T> {
match self.pump_events(None, &mut event_handler) {
PumpStatus::Exit(0) => {
break Ok(());
}
},
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
}
},
_ => {
continue;
}
},
}
};
@@ -256,7 +240,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
}
},
};
min_timeout(control_flow_timeout, timeout)
};
@@ -274,11 +258,12 @@ impl<T: 'static> EventLoop<T> {
if let Err(error) = self.loop_dispatch(timeout) {
// NOTE We exit on errors from dispatches, since if we've got protocol error
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
// really an option. Instead we inform that the event loop got destroyed. We may
// communicate an error that something was terminated, but winit doesn't provide us
// with an API to do that via some event.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is
// not really an option. Instead we inform that the event loop got
// destroyed. We may communicate an error that something was
// terminated, but winit doesn't provide us with an API to do that
// via some event. Still, we set the exit code to the error's OS
// error code, or to 1 if not possible.
let exit_code = error.raw_os_error().unwrap_or(1);
self.set_exit_code(exit_code);
return;
@@ -288,23 +273,14 @@ impl<T: 'static> EventLoop<T> {
// to be considered here
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled {
start,
requested_resume: None,
},
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled {
start,
requested_resume: Some(deadline),
}
StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
} else {
StartCause::ResumeTimeReached {
start,
requested_resume: deadline,
}
StartCause::ResumeTimeReached { start, requested_resume: deadline }
}
}
},
};
// Reduce spurious wake-ups.
@@ -471,13 +447,8 @@ impl<T: 'static> EventLoop<T> {
return Some(WindowEvent::Destroyed);
}
let mut window = state
.windows
.get_mut()
.get_mut(window_id)
.unwrap()
.lock()
.unwrap();
let mut window =
state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap();
if window.frame_callback_state() == FrameCallbackState::Requested {
return None;
@@ -485,10 +456,8 @@ impl<T: 'static> EventLoop<T> {
// Reset the frame callbacks state.
window.frame_callback_reset();
let mut redraw_requested = window_requests
.get(window_id)
.unwrap()
.take_redraw_requested();
let mut redraw_requested =
window_requests.get(window_id).unwrap().take_redraw_requested();
// Redraw the frame while at it.
redraw_requested |= window.refresh_frame();
@@ -498,10 +467,7 @@ impl<T: 'static> EventLoop<T> {
if let Some(event) = event {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
event,
},
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
}
@@ -532,7 +498,7 @@ impl<T: 'static> EventLoop<T> {
}
refresh
}
},
None => false,
});
}
@@ -545,7 +511,7 @@ impl<T: 'static> EventLoop<T> {
match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.ping();
}
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
}
@@ -599,9 +565,7 @@ impl<T: 'static> EventLoop<T> {
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
))))
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})
}

View File

@@ -13,9 +13,7 @@ pub struct EventLoopProxy<T: 'static> {
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
}
EventLoopProxy { user_events_sender: self.user_events_sender.clone() }
}
}
@@ -25,8 +23,6 @@ impl<T: 'static> EventLoopProxy<T> {
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|SendError(error)| EventLoopClosed(error))
self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error))
}
}

View File

@@ -38,10 +38,7 @@ impl EventSink {
/// 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: RootWindowId(window_id) });
}
#[inline]

View File

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

View File

@@ -11,11 +11,7 @@ use super::event_loop::ActiveEventLoop;
impl ActiveEventLoop {
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.state
.borrow()
.output_state
.outputs()
.map(MonitorHandle::new)
self.state.borrow().output_state.outputs().map(MonitorHandle::new)
}
#[inline]
@@ -52,9 +48,7 @@ impl MonitorHandle {
pub fn size(&self) -> PhysicalSize<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let dimensions = output_data.with_output_info(|info| {
info.modes
.iter()
.find_map(|mode| mode.current.then_some(mode.dimensions))
info.modes.iter().find_map(|mode| mode.current.then_some(mode.dimensions))
});
match dimensions {
@@ -85,9 +79,7 @@ impl MonitorHandle {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
info.modes
.iter()
.find_map(|mode| mode.current.then_some(mode.refresh_rate as u32))
info.modes.iter().find_map(|mode| mode.current.then_some(mode.refresh_rate as u32))
})
}

View File

@@ -7,9 +7,8 @@ use calloop::timer::{TimeoutAction, Timer};
use calloop::{LoopHandle, RegistrationToken};
use tracing::warn;
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_keyboard::{
Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat,
Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, WlKeyboard,
};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
@@ -19,7 +18,6 @@ use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::seat::WinitSeatState;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
@@ -34,7 +32,17 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
) {
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return,
None => {
warn!("Received keyboard event {event:?} without seat");
return;
},
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => {
warn!("Received keyboard event {event:?} without keyboard");
return;
},
};
match event {
@@ -42,16 +50,16 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
WEnum::Value(format) => match format {
WlKeymapFormat::NoKeymap => {
warn!("non-xkb compatible keymap")
}
},
WlKeymapFormat::XkbV1 => {
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let context = &mut keyboard_state.xkb_context;
context.set_keymap_from_fd(fd, size as usize);
}
},
_ => unreachable!(),
},
WEnum::Unknown(value) => {
warn!("unknown keymap format 0x{:x}", value)
}
},
},
WlKeyboardEvent::Enter { surface, .. } => {
let window_id = wayland::make_wid(&surface);
@@ -63,12 +71,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let was_unfocused = !window.has_focus();
window.add_seat_focus(data.seat.id());
was_unfocused
}
},
None => return,
};
// Drop the repeat, if there were any.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -78,9 +85,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// The keyboard focus is considered as general focus.
if was_unfocused {
state
.events_sink
.push_window_event(WindowEvent::Focused(true), window_id);
state.events_sink.push_window_event(WindowEvent::Focused(true), window_id);
}
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
@@ -90,13 +95,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
window_id,
);
}
}
},
WlKeyboardEvent::Leave { surface, .. } => {
let window_id = wayland::make_wid(&surface);
// NOTE: we should drop the repeat regardless whethere it was for the present
// window of for the window which just went gone.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -109,7 +113,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let mut window = window.lock().unwrap();
window.remove_seat_focus(&data.seat.id());
window.has_focus()
}
},
None => return,
};
@@ -124,20 +128,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
window_id,
);
state
.events_sink
.push_window_event(WindowEvent::Focused(false), window_id);
state.events_sink.push_window_event(WindowEvent::Focused(false), window_id);
}
}
WlKeyboardEvent::Key {
key,
state: WEnum::Value(WlKeyState::Pressed),
..
} => {
},
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Pressed), .. } => {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -145,18 +143,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let delay = match keyboard_state.repeat_info {
RepeatInfo::Repeat { delay, .. } => delay,
RepeatInfo::Disable => return,
};
if !keyboard_state
.xkb_context
.keymap_mut()
.unwrap()
.key_repeats(key)
{
if !keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key) {
return;
}
@@ -177,18 +169,25 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to
// `None` is instant.
let repeat_keycode =
match seat_state.keyboard_state.as_ref().unwrap().current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to `None`
// is instant.
let repeat_keycode = match keyboard_state.current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
repeat_keycode,
@@ -197,22 +196,18 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
// NOTE: the gap could change dynamically while repeat is going.
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
match keyboard_state.repeat_info {
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
RepeatInfo::Disable => TimeoutAction::Drop,
}
})
.ok();
}
WlKeyboardEvent::Key {
key,
state: WEnum::Value(WlKeyState::Released),
..
} => {
},
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => {
let key = key + 8;
key_input(
seat_state,
keyboard_state,
&mut state.events_sink,
data,
key,
@@ -220,13 +215,8 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
if keyboard_state.repeat_info != RepeatInfo::Disable
&& keyboard_state
.xkb_context
.keymap_mut()
.unwrap()
.key_repeats(key)
&& keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key)
&& Some(key) == keyboard_state.current_repeat
{
keyboard_state.current_repeat = None;
@@ -234,15 +224,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
keyboard_state.loop_handle.remove(token);
}
}
}
},
WlKeyboardEvent::Modifiers {
mods_depressed,
mods_latched,
mods_locked,
group,
..
mods_depressed, mods_latched, mods_locked, group, ..
} => {
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let xkb_context = &mut keyboard_state.xkb_context;
let xkb_state = match xkb_context.state_mut() {
Some(state) => state,
None => return,
@@ -257,16 +243,15 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
None => {
seat_state.modifiers_pending = true;
return;
}
},
};
state.events_sink.push_window_event(
WindowEvent::ModifiersChanged(seat_state.modifiers.into()),
window_id,
);
}
},
WlKeyboardEvent::RepeatInfo { rate, delay } => {
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.repeat_info = if rate == 0 {
// Stop the repeat once we get a disable event.
keyboard_state.current_repeat = None;
@@ -279,7 +264,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let delay = Duration::from_millis(delay as u64);
RepeatInfo::Repeat { gap, delay }
};
}
},
_ => unreachable!(),
}
}
@@ -353,10 +338,7 @@ impl Default for RepeatInfo {
///
/// The values are picked based on the default in various compositors and Xorg.
fn default() -> Self {
Self::Repeat {
gap: Duration::from_millis(40),
delay: Duration::from_millis(200),
}
Self::Repeat { gap: Duration::from_millis(40), delay: Duration::from_millis(200) }
}
}
@@ -372,15 +354,12 @@ pub struct KeyboardData {
impl KeyboardData {
pub fn new(seat: WlSeat) -> Self {
Self {
window_id: Default::default(),
seat,
}
Self { window_id: Default::default(), seat }
}
}
fn key_input(
seat_state: &mut WinitSeatState,
keyboard_state: &mut KeyboardState,
event_sink: &mut EventSink,
data: &KeyboardData,
keycode: u32,
@@ -392,16 +371,10 @@ fn key_input(
None => return,
};
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);
let event = WindowEvent::KeyboardInput {
device_id,
event,
is_synthetic: false,
};
let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false };
event_sink.push_window_event(event, window_id);
}
}

View File

@@ -3,6 +3,7 @@
use std::sync::Arc;
use ahash::AHashMap;
use tracing::warn;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -76,17 +77,23 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::new_capability for unknown seat");
return;
},
};
match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok();
}
},
SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => {
let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone()));
seat_state.keyboard_state =
Some(KeyboardState::new(keyboard, self.loop_handle.clone()));
}
},
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let surface_id = surface.id();
@@ -114,19 +121,15 @@ impl SeatHandler for WinitState {
let themed_pointer = Arc::new(themed_pointer);
// Register cursor surface.
self.pointer_surfaces
.insert(surface_id, themed_pointer.clone());
self.pointer_surfaces.insert(surface_id, themed_pointer.clone());
seat_state.pointer = Some(themed_pointer);
}
},
_ => (),
}
if let Some(text_input_state) = seat_state
.text_input
.is_none()
.then_some(self.text_input_state.as_ref())
.flatten()
if let Some(text_input_state) =
seat_state.text_input.is_none().then_some(self.text_input_state.as_ref()).flatten()
{
seat_state.text_input = Some(Arc::new(text_input_state.get_text_input(
&seat,
@@ -143,7 +146,13 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::remove_capability for unknown seat");
return;
},
};
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
@@ -156,7 +165,7 @@ impl SeatHandler for WinitState {
touch.release();
}
}
}
},
SeatCapability::Pointer => {
if let Some(relative_pointer) = seat_state.relative_pointer.take() {
relative_pointer.destroy();
@@ -177,11 +186,11 @@ impl SeatHandler for WinitState {
pointer.pointer().release();
}
}
}
},
SeatCapability::Keyboard => {
seat_state.keyboard_state = None;
self.on_keyboard_destroy(&seat.id());
}
},
_ => (),
}
}
@@ -213,8 +222,7 @@ impl WinitState {
let had_focus = window.has_focus();
window.remove_seat_focus(seat);
if had_focus != window.has_focus() {
self.events_sink
.push_window_event(WindowEvent::Focused(false), *window_id);
self.events_sink.push_window_event(WindowEvent::Focused(false), *window_id);
}
}
}

View File

@@ -4,6 +4,8 @@ use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::warn;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -19,8 +21,9 @@ use sctk::reexports::csd_frame::FrameClick;
use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData;
use sctk::seat::pointer::{PointerData, PointerDataExt};
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
use sctk::seat::pointer::{
PointerData, PointerDataExt, PointerEvent, PointerEventKind, PointerHandler,
};
use sctk::seat::SeatState;
use crate::dpi::{LogicalPosition, PhysicalPosition};
@@ -40,7 +43,21 @@ impl PointerHandler for WinitState {
events: &[PointerEvent],
) {
let seat = pointer.winit_data().seat();
let seat_state = self.seats.get(&seat.id()).unwrap();
let seat_state = match self.seats.get(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received pointer event without seat");
return;
},
};
let themed_pointer = match seat_state.pointer.as_ref() {
Some(pointer) => pointer,
None => {
warn!("Received pointer event without pointer");
return;
},
};
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
@@ -77,24 +94,16 @@ impl PointerHandler for WinitState {
event.position.0,
event.position.1,
) {
if let Some(pointer) = seat_state.pointer.as_ref() {
let _ = pointer.set_cursor(connection, icon);
}
let _ = themed_pointer.set_cursor(connection, icon);
}
}
},
PointerEventKind::Leave { .. } if parent_surface != surface => {
window.frame_point_left();
}
ref kind @ PointerEventKind::Press {
button,
serial,
time,
}
| ref kind @ PointerEventKind::Release {
button,
serial,
time,
} if parent_surface != surface => {
},
ref kind @ PointerEventKind::Press { button, serial, time }
| ref kind @ PointerEventKind::Release { button, serial, time }
if parent_surface != surface =>
{
let click = match wayland_button_to_winit(button) {
MouseButton::Left => FrameClick::Normal,
MouseButton::Right => FrameClick::Alternate,
@@ -112,56 +121,41 @@ impl PointerHandler for WinitState {
window_id,
&mut self.window_compositor_updates,
);
}
},
// Regular events on the main surface.
PointerEventKind::Enter { .. } => {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_entered(pointer);
}
window.pointer_entered(Arc::downgrade(themed_pointer));
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
self.events_sink.push_window_event(
WindowEvent::CursorMoved {
device_id,
position,
},
WindowEvent::CursorMoved { device_id, position },
window_id,
);
}
},
PointerEventKind::Leave { .. } => {
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_left(pointer);
}
window.pointer_left(Arc::downgrade(themed_pointer));
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
self.events_sink
.push_window_event(WindowEvent::CursorLeft { device_id }, window_id);
}
},
PointerEventKind::Motion { .. } => {
self.events_sink.push_window_event(
WindowEvent::CursorMoved {
device_id,
position,
},
WindowEvent::CursorMoved { device_id, position },
window_id,
);
}
},
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
// Update the last button serial.
pointer
.winit_data()
.inner
.lock()
.unwrap()
.latest_button_serial = serial;
pointer.winit_data().inner.lock().unwrap().latest_button_serial = serial;
let button = wayland_button_to_winit(button);
let state = if matches!(kind, PointerEventKind::Press { .. }) {
@@ -170,19 +164,11 @@ impl PointerHandler for WinitState {
ElementState::Released
};
self.events_sink.push_window_event(
WindowEvent::MouseInput {
device_id,
state,
button,
},
WindowEvent::MouseInput { device_id, state, button },
window_id,
);
}
PointerEventKind::Axis {
horizontal,
vertical,
..
} => {
},
PointerEventKind::Axis { horizontal, vertical, .. } => {
// Get the current phase.
let mut pointer_data = pointer.winit_data().inner.lock().unwrap();
@@ -209,13 +195,13 @@ impl PointerHandler for WinitState {
// Mice events have both pixel and discrete delta's at the same time. So prefer
// the descrite values if they are present.
let delta = if has_discrete_scroll {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::LineDelta(
(-horizontal.discrete) as f32,
(-vertical.discrete) as f32,
)
} else {
// XXX Wayland sign convention is the inverse of winit.
// NOTE: Wayland sign convention is the inverse of winit.
MouseScrollDelta::PixelDelta(
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
.to_physical(scale_factor),
@@ -223,14 +209,10 @@ impl PointerHandler for WinitState {
};
self.events_sink.push_window_event(
WindowEvent::MouseWheel {
device_id,
delta,
phase,
},
WindowEvent::MouseWheel { device_id, delta, phase },
window_id,
)
}
},
}
}
}
@@ -407,8 +389,7 @@ pub trait WinitPointerDataExt {
impl WinitPointerDataExt for WlPointer {
fn winit_data(&self) -> &WinitPointerData {
self.data::<WinitPointerData>()
.expect("failed to get pointer data.")
self.data::<WinitPointerData>().expect("failed to get pointer data.")
}
}
@@ -422,14 +403,13 @@ impl PointerConstraintsState {
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self {
pointer_constraints,
})
Ok(Self { pointer_constraints })
}
}
impl Deref for PointerConstraintsState {
type Target = ZwpPointerConstraintsV1;
fn deref(&self) -> &Self::Target {
&self.pointer_constraints
}

View File

@@ -61,31 +61,19 @@ impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerS
_qhandle: &QueueHandle<WinitState>,
) {
let (dx_unaccel, dy_unaccel) = match event {
zwp_relative_pointer_v1::Event::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} => (dx_unaccel, dy_unaccel),
zwp_relative_pointer_v1::Event::RelativeMotion { dx_unaccel, dy_unaccel, .. } => {
(dx_unaccel, dy_unaccel)
},
_ => return,
};
state
.events_sink
.push_device_event(DeviceEvent::Motion { axis: 0, value: dx_unaccel }, super::DeviceId);
state
.events_sink
.push_device_event(DeviceEvent::Motion { axis: 1, value: dy_unaccel }, super::DeviceId);
state.events_sink.push_device_event(
DeviceEvent::Motion {
axis: 0,
value: dx_unaccel,
},
super::DeviceId,
);
state.events_sink.push_device_event(
DeviceEvent::Motion {
axis: 1,
value: dy_unaccel,
},
super::DeviceId,
);
state.events_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel) },
super::DeviceId,
);
}

View File

@@ -3,14 +3,12 @@ use std::ops::Deref;
use sctk::globals::GlobalData;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Dispatch};
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ContentHint, ContentPurpose, ZwpTextInputV3,
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::{Ime, WindowEvent};
@@ -77,13 +75,11 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
text_input.enable();
text_input.set_content_type_by_purpose(window.ime_purpose());
text_input.commit();
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
window.text_input_entered(text_input);
}
},
TextInputEvent::Leave { surface } => {
text_input_data.surface = None;
@@ -102,15 +98,9 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
window.text_input_left(text_input);
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
},
TextInputEvent::PreeditString { text, cursor_begin, cursor_end } => {
let text = text.unwrap_or_default();
let cursor_begin = usize::try_from(cursor_begin)
.ok()
@@ -119,16 +109,12 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
text_input_data.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
})
}
text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end })
},
TextInputEvent::CommitString { text } => {
text_input_data.pending_preedit = None;
text_input_data.pending_commit = text;
}
},
TextInputEvent::Done { .. } => {
let window_id = match text_input_data.surface.as_ref() {
Some(surface) => wayland::make_wid(surface),
@@ -150,20 +136,19 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
// Send preedit.
if let Some(preedit) = text_input_data.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
let cursor_range =
preedit.cursor_begin.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
window_id,
);
}
}
},
TextInputEvent::DeleteSurroundingText { .. } => {
// Not handled.
}
_ => {}
},
_ => {},
}
}
}

View File

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

View File

@@ -135,7 +135,7 @@ impl WinitState {
Err(e) => {
tracing::warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
None
}
},
};
let output_state = OutputState::new(globals, queue_handle);
@@ -218,8 +218,7 @@ impl WinitState {
{
pos
} else {
self.window_compositor_updates
.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
@@ -240,9 +239,7 @@ impl WinitState {
}
pub fn queue_close(updates: &mut Vec<WindowCompositorUpdate>, window_id: WindowId) {
let pos = if let Some(pos) = updates
.iter()
.position(|update| update.window_id == window_id)
let pos = if let Some(pos) = updates.iter().position(|update| update.window_id == window_id)
{
pos
} else {
@@ -276,15 +273,12 @@ impl WindowHandler for WinitState {
) {
let window_id = super::make_wid(window.wl_surface());
let pos = if let Some(pos) = self
.window_compositor_updates
.iter()
.position(|update| update.window_id == window_id)
let pos = if let Some(pos) =
self.window_compositor_updates.iter().position(|update| update.window_id == window_id)
{
pos
} else {
self.window_compositor_updates
.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
@@ -318,10 +312,7 @@ impl OutputHandler for WinitState {
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, output: WlOutput) {
self.monitors
.lock()
.unwrap()
.push(MonitorHandle::new(output));
self.monitors.lock().unwrap().push(MonitorHandle::new(output));
}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, updated: WlOutput) {
@@ -348,12 +339,30 @@ impl CompositorHandler for WinitState {
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: &WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn scale_factor_changed(
&mut self,
_: &Connection,
@@ -388,11 +397,11 @@ impl CompositorHandler for WinitState {
}
impl ProvidesRegistryState for WinitState {
sctk::registry_handlers![OutputState, SeatState];
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
sctk::registry_handlers![OutputState, SeatState];
}
// The window update coming from the compositor.
@@ -413,12 +422,7 @@ pub struct WindowCompositorUpdate {
impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self {
Self {
window_id,
resized: false,
scale_changed: false,
close_window: false,
}
Self { window_id, resized: false, scale_changed: false, close_window: false }
}
}

View File

@@ -2,11 +2,9 @@
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use wayland_protocols_plasma::blur::client::{
org_kde_kwin_blur::OrgKdeKwinBlur, org_kde_kwin_blur_manager::OrgKdeKwinBlurManager,
};
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur_manager::OrgKdeKwinBlurManager;
use sctk::globals::GlobalData;

View File

@@ -2,11 +2,11 @@
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::{
Event as FractionalScalingEvent, WpFractionalScaleV1,
};
use sctk::globals::GlobalData;
@@ -41,11 +41,8 @@ impl FractionalScalingManager {
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> WpFractionalScaleV1 {
let data = FractionalScaling {
surface: surface.clone(),
};
self.manager
.get_fractional_scale(surface, queue_handle, data)
let data = FractionalScaling { surface: surface.clone() };
self.manager.get_fractional_scale(surface, queue_handle, data)
}
}

View File

@@ -2,8 +2,7 @@
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
@@ -33,8 +32,7 @@ impl ViewporterState {
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> WpViewport {
self.viewporter
.get_viewport(surface, queue_handle, GlobalData)
self.viewporter.get_viewport(surface, queue_handle, GlobalData)
}
}

View File

@@ -3,12 +3,9 @@
use std::sync::atomic::AtomicBool;
use std::sync::Weak;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::globals::BindError;
use sctk::reexports::client::globals::GlobalList;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{
Event as ActivationTokenEvent, XdgActivationTokenV1,
};
@@ -78,7 +75,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
if let Some(attention_requested) = fence.upgrade() {
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
}
}
},
XdgActivationTokenData::Obtain((window_id, serial)) => {
state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone {
@@ -87,7 +84,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
},
*window_id,
);
}
},
}
proxy.destroy();

View File

@@ -5,13 +5,11 @@ use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_display::WlDisplay;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use sctk::reexports::client::QueueHandle;
use sctk::reexports::client::{Proxy, QueueHandle};
use sctk::compositor::{CompositorState, Region, SurfaceData};
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::shell::xdg::window::Window as SctkWindow;
use sctk::shell::xdg::window::WindowDecorations;
use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations};
use sctk::shell::WaylandSurface;
use tracing::warn;
@@ -74,7 +72,7 @@ pub struct Window {
/// Source to wake-up the event-loop for window requests.
event_loop_awakener: calloop::ping::Ping,
/// The event sink to deliver sythetic events.
/// The event sink to deliver synthetic events.
window_events_sink: Arc<Mutex<EventSink>>,
}
@@ -90,15 +88,11 @@ impl Window {
let surface = state.compositor_state.create_surface(&queue_handle);
let compositor = state.compositor_state.clone();
let xdg_activation = state
.xdg_activation
.as_ref()
.map(|activation_state| activation_state.global().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 size: Size = attributes
.inner_size
.unwrap_or(LogicalSize::new(800., 600.).into());
let size: Size = attributes.inner_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.
@@ -109,9 +103,7 @@ impl Window {
};
let window =
state
.xdg_shell
.create_window(surface.clone(), default_decorations, &queue_handle);
state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
let mut window_state = WindowState::new(
event_loop_window_target.connection.clone(),
@@ -152,7 +144,8 @@ impl Window {
match attributes.fullscreen.map(Into::into) {
Some(Fullscreen::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 {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
@@ -161,7 +154,7 @@ impl Window {
});
window.set_fullscreen(output.as_ref())
}
},
_ if attributes.maximized => window.set_maximized(),
_ => (),
};
@@ -172,10 +165,9 @@ impl Window {
}
// Activate the window when the token is passed.
if let (Some(xdg_activation), Some(token)) = (
xdg_activation.as_ref(),
attributes.platform_specific.activation_token,
) {
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{
xdg_activation.activate(token._token, &surface);
}
@@ -185,20 +177,14 @@ impl Window {
// Add the window and window requests into the state.
let window_state = Arc::new(Mutex::new(window_state));
let window_id = super::make_wid(&surface);
state
.windows
.get_mut()
.insert(window_id, window_state.clone());
state.windows.get_mut().insert(window_id, window_state.clone());
let window_requests = WindowRequests {
redraw_requested: AtomicBool::new(true),
closed: AtomicBool::new(false),
};
let window_requests = Arc::new(window_requests);
state
.window_requests
.get_mut()
.insert(window_id, window_requests.clone());
state.window_requests.get_mut().insert(window_id, window_requests.clone());
// Setup the event sync to insert `WindowEvents` right from the window.
let window_events_sink = state.window_events_sink.clone();
@@ -208,17 +194,13 @@ impl Window {
// Do a roundtrip.
event_queue.roundtrip(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
error
))))
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
// 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
))))
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
}
@@ -328,10 +310,7 @@ impl Window {
pub fn set_min_inner_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_inner_size(min_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
@@ -341,10 +320,7 @@ impl Window {
pub fn set_max_inner_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_inner_size(max_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
@@ -361,10 +337,7 @@ impl Window {
#[inline]
pub fn set_transparent(&self, transparent: bool) {
self.window_state
.lock()
.unwrap()
.set_transparent(transparent);
self.window_state.lock().unwrap().set_transparent(transparent);
}
#[inline]
@@ -387,10 +360,7 @@ impl Window {
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window_state
.lock()
.unwrap()
.drag_resize_window(direction)
self.window_state.lock().unwrap().drag_resize_window(direction)
}
#[inline]
@@ -498,7 +468,8 @@ impl Window {
match fullscreen {
Some(Fullscreen::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 {
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
@@ -507,7 +478,7 @@ impl Window {
});
self.window.set_fullscreen(output.as_ref())
}
},
None => self.window.unset_fullscreen(),
}
}
@@ -524,10 +495,7 @@ impl Window {
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window_state
.lock()
.unwrap()
.set_cursor_visible(visible);
self.window_state.lock().unwrap().set_cursor_visible(visible);
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
@@ -536,7 +504,7 @@ impl Window {
None => {
warn!("`request_user_attention` isn't supported");
return;
}
},
};
// Urgency is only removed by the compositor and there's no need to raise urgency when it
@@ -628,10 +596,7 @@ impl Window {
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.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
self.event_loop_awakener.ping();
}
}

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