Compare commits

..

78 Commits

Author SHA1 Message Date
Mads Marquart
bf73a978df Only run observers in the default run loop mode 2024-06-29 00:27:31 +02:00
Mads Marquart
82d9bbe559 Remove EventLoopExtIOS::idiom and ios::Idiom (#2924)
Introduced in 3a7350c with unclear motivation.

Nowadays, this feature is incomplete and unsound, and the equivalent
functionality can be trivially achieved outside of `winit` using
`objc2-ui-kit`.
2024-06-24 22:56:20 +02:00
Mads Marquart
8bdd4d620e Use self:: when re-importing local module (#3757)
Follow-up to https://github.com/rust-windowing/winit/pull/3755.
2024-06-24 22:40:49 +02:00
Mads Marquart
9d5412ffe1 Move iOS and macOS implementations into new apple module (#3756)
Move iOS and macOS implementations to a shared folder called `apple`, to allow
us to reduce the code-duplication between these platforms in the future.

The folder structure is now:
- `src/platform_impl/apple/`
  - `appkit/`
  - `uikit/`
  - `example_shared_file.rs`
  - `mod.rs`

* Add preliminary support for tvOS, watchOS and visionOS
* Reduce duplication in Cargo.toml when specifying dependencies
2024-06-24 13:26:49 +02:00
Kirill Chibisov
ecb887e5c3 event_loop: remove generic user event
Let the users wake up the event loop and then they could poll their
user sources.

Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-06-24 13:04:55 +03:00
Mads Marquart
7d1287958f 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-06-24 03:57:48 +02:00
Mads Marquart
c0c14aaf00 macOS: Call ApplicationHandler directly instead of using Event (#3753)
Additionally, always queue events in `handle_scale_factor_changed`.

These events were intentionally not queued before, since they are
executed inside `handle_scale_factor_changed`, which is itself queued.
Though that's a bit too subtle, so let's just take the minuscule perf
hit of redundantly checking whether we need to queue again.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-06-23 23:14:22 +02:00
John Nunley
10dc0674bb 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-06-23 12:28:58 -07:00
msiglreith
4f59796e8a Add notgull as Windows maintainer 2024-06-22 08:41:37 -07:00
msiglreith
32097d75c7 Update codeowner list 2024-06-22 08:41:37 -07:00
Bruce Mitchener
c6c4395c3b 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-06-22 11:38:42 +02:00
Kirill Chibisov
38e6f9ad84 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-21 21:40:16 +03:00
daxpedda
3e6092b8ed Web: implement WaitUntilStrategy (#3739) 2024-06-20 23:07:42 +02:00
daxpedda
b4e83a5966 Web: set control flow strategies on EventLoop (#3740) 2024-06-20 22:56:08 +02:00
Mads Marquart
db2c97a995 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-20 17:05:34 +03:00
daxpedda
1552eb21f7 Bump MSRV to v1.73 (#3743) 2024-06-20 11:09:15 +02:00
Kirill Chibisov
d8ffd4bb26 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-17 13:51:08 +03:00
Kirill Chibisov
34c15608e0 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-15 23:50:40 +03:00
Kirill Chibisov
eef6977f45 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 15:41:34 +03:00
Kirill Chibisov
078b46720b chore: address 1.79 clippy lints 2024-06-15 15:26:26 +03:00
daxpedda
3b4e064a07 Web: fix crash InnerSizeWriter::request_inner_size() (#3727) 2024-06-12 00:22:03 +02:00
daxpedda
39bc139500 Web: don't overwrite cursor with CursorIcon::Default (#3729) 2024-06-12 00:12:14 +02:00
daxpedda
9522670081 Web: queue EventLoopProxy::send_event() to microtask 2024-06-12 00:02:26 +02:00
Kirill Chibisov
9a1ef49dc3 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-10 17:13:33 -07:00
Mads Marquart
3a624e0f52 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-06 14:39:31 +02:00
Mads Marquart
279e3edc54 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-06 12:32:02 +02:00
Philippe Renon
0e74d37ff5 doc: clarify Window::pre_present_notify availability
Fixes #3703.
2024-06-06 12:11:06 +03:00
ShikiSuen
2d1382f7d6 Handle _selected_range sent to NSTextInputClient.setMarkedText(). (#3619)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-05-31 08:54:20 +02:00
Mads Marquart
5d8091fc7f Implement ApplicationHandler for &mut A and Box<A> (#3709) 2024-05-29 11:51:53 +02:00
Mads Marquart
d7abe0316e Update objc2 to v0.2.2 (#3702)
- Use new `bitflags!` support.
- Use `objc2-ui-kit`.
- Change usage of `Id` to `Retained`.
2024-05-27 14:49:22 +02:00
Diggory Hardy
5ea20fc905 event_loop: add is_x11 and is_wayland on EventLoop 2024-05-26 17:38:10 +04:00
Golden Water
e108fa2fbf 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-05-23 20:40:07 +04:00
Kirill Chibisov
fff6788c12 chore: explicitly use cfg_aliases 0.2.1
This correctly handles recent nightly lint that requires to explicitly
define the CFG guards.
2024-05-22 15:51:29 +04:00
Kirill Chibisov
3e8fa41073 event_loop: remove deprecated run APIs
The APIs are not well suited for the `&dyn ApplicationHandler` model and
`Box<dyn EventLoop>` structure, thus remove them.
2024-05-20 20:27:36 +04:00
Kevin Müller
2b1c8cea1b 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-05-19 15:08:14 -07:00
Ryan Burleson
ab33fb8eda fix doc typo in application.rs (#3676) 2024-05-07 21:24:02 +02:00
linkmauve
b0b64a9a15 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-05-06 19:50:25 +04:00
Mads Marquart
d5d202d60e Reduce usage of direct msg_send! 2024-05-06 19:09:38 +04:00
Mads Marquart
cb39ab29f4 macOS: Move util::EMPTY_RANGE to usage spot (#3685) 2024-05-06 16:54:52 +02:00
Mads Marquart
0a3cacd577 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-05-06 18:29:07 +04:00
Mads Marquart
16fd2baba0 Use rustc-check-cfg (#3682) 2024-05-06 07:11:57 +02:00
daxpedda
7f8771a362 Web: fix Clippy v1.78 FPs (#3678) 2024-05-03 22:24:54 +02:00
Kirill Chibisov
337d50779c Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-04-27 20:02:40 +04:00
Joshua Pedrick
fd477986de 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 15:55:04 +02:00
Mads Marquart
94664ff687 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 15:41:14 +02:00
growfrow
0fc8c37721 chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 15:29:11 +02:00
Kirill Chibisov
ec29c81ad2 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 17:03:28 +04:00
Marijn Suijten
304a585493 android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 13:35:11 +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
208 changed files with 7489 additions and 10583 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 # 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. # 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

20
.github/CODEOWNERS vendored
View File

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

View File

@@ -10,8 +10,8 @@ jobs:
name: Check formatting name: Check formatting
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: taiki-e/checkout-action@v1
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@nightly
with: with:
components: rustfmt components: rustfmt
- name: Check Formatting - name: Check Formatting
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v4 - uses: taiki-e/checkout-action@v1
- uses: taiki-e/install-action@v2 - uses: taiki-e/install-action@v2
with: with:
tool: typos-cli tool: typos-cli
@@ -42,7 +42,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.70.0'] toolchain: [stable, nightly, '1.73']
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -61,10 +61,10 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude: exclude:
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.70.0' - toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include: include:
- toolchain: '1.70.0' - toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly' - toolchain: 'nightly'
platform: { platform: {
@@ -88,7 +88,7 @@ jobs:
CMD: ${{ matrix.platform.cmd }} CMD: ${{ matrix.platform.cmd }}
steps: steps:
- uses: actions/checkout@v3 - uses: taiki-e/checkout-action@v1
- name: Restore cache of cargo folder - name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after # We use `restore` and later `save`, so that we can create the key after
@@ -149,13 +149,13 @@ jobs:
- name: Test dpi crate - name: Test dpi crate
if: > if: >
contains(matrix.platform.name, 'Linux 64bit') && contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.73'
run: cargo test -p dpi run: cargo test -p dpi
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test --no-run $OPTIONS
- name: Run tests - name: Run tests
@@ -164,7 +164,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
@@ -174,7 +174,7 @@ jobs:
- name: Build tests with serde enabled - name: Build tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
@@ -183,7 +183,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0' matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS --features serde run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation - name: Check docs.rs documentation
@@ -202,41 +202,6 @@ jobs:
~/.cargo/git/db/ ~/.cargo/git/db/
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }} key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
it:
name: Run integration tests on ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly]
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
env:
# Set more verbose terminal output
CARGO_TERM_VERBOSE: true
RUST_BACKTRACE: 1
# Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- name: Log into GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: Common tests
run: cargo run -p gui-test-runner -- common-tests ${{ matrix.platform.target }}
cargo-deny: cargo-deny:
name: Run cargo-deny on ${{ matrix.platform.name }} name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -255,9 +220,24 @@ jobs:
- { name: 'Windows', target: x86_64-pc-windows-gnu } - { name: 'Windows', target: x86_64-pc-windows-gnu }
steps: steps:
- uses: actions/checkout@v3 - uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v1 - uses: EmbarkStudios/cargo-deny-action@v1
with: with:
command: check command: check
log-level: error log-level: error
arguments: --all-features --target ${{ matrix.platform.target }} 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 id-token: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:

3
.gitignore vendored
View File

@@ -3,8 +3,5 @@ target/
rls/ rls/
.vscode/ .vscode/
*~ *~
*.wasm
*.ts
*.js
#*# #*#
.DS_Store .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 This document contains guidelines for contributing code to winit. It has to be
[See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should followed in order for your patch to be approved and applied.
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.
## 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 To save your time it's wise to check already opened [pull requests][prs] and
your description of the issue as detailed as possible: [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 ### Submitting your work and handling review
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
## 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. During the review process certain events could require an action from your side,
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms common patterns and reactions are described below.
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.
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will _Event:_ The CI fails to build, but it looks like it is not your fault. Not
handle those for you when merging (see below). communicating so could result in maintainers not looking into your patch,
- if your PR affects the platform compatibility of one or more features or adds another feature, the since they may assume that you're still working on it.\
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features) _Desired behaviour:_ Write a message saying roughly the following "The CI
should be updated. 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 _Event:_ Maintainer requested changes to your PR.\
is that a PR must be approved by at least two maintainers of winit before being merged, including _Desired behavior:_ Once you address the request, you should re-request a review
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it). 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 _Event:_ You've opened a PR, but maintainer shortly after commented that they
`changelog` module (but you must resolve other conflicts yourself). Doing this requires that you check the want to work on that themselves.\
"give contributors write access to the branch" checkbox when creating the PR. _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 ## 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 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. 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` 1. Initially, the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release 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` 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` 5. In the branch, the version is bumped to `v0.2.0`
6. The new commit in the branch is tagged `v0.2.0` 6. The new commit in the branch is tagged `v0.2.0`
7. The version is pushed to crates.io 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 2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x` 3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 4-9 of the regular release example 4. Follow steps 4-9 of the regular release example
[CODEOWNERS]: .github/CODEOWNERS

View File

@@ -1,7 +1,10 @@
[package] [package]
name = "winit" name = "winit"
version = "0.29.15" version = "0.30.3"
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"] authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
]
description = "Cross-platform window creation library." description = "Cross-platform window creation library."
keywords = ["windowing"] keywords = ["windowing"]
readme = "README.md" readme = "README.md"
@@ -11,6 +14,7 @@ rust-version.workspace = true
repository.workspace = true repository.workspace = true
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
exclude = ["/.cargo"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ features = [
@@ -45,7 +49,15 @@ rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] 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-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
@@ -59,95 +71,137 @@ rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"] rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
[build-dependencies] [build-dependencies]
cfg_aliases = "0.2.0" cfg_aliases = "0.2.1"
[dependencies] [dependencies]
bitflags = "2" bitflags = "2"
cursor-icon = "1.1.0" cursor-icon = "1.1.0"
dpi = { version = "0.1.1", path = "dpi" } dpi = { version = "0.1.1", path = "dpi" }
rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } 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_05 = { package = "raw-window-handle", version = "0.5.2", features = [
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true } "std",
], optional = true }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
"std",
], optional = true }
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
smol_str = "0.2.0" smol_str = "0.2.0"
tracing = { version = "0.1.40", default_features = false } tracing = { version = "0.1.40", default-features = false }
[dev-dependencies] [dev-dependencies]
image = { version = "0.24.0", default-features = false, features = ["png"] } image = { version = "0.25.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default_features = false, features = ["log"] } tracing = { version = "0.1.40", default-features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
winit = { path = ".", features = ["rwh_05"] } winit = { path = ".", features = ["rwh_05"] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies] [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",
] }
# Android
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.5.0" android-activity = "0.6.0"
ndk = { version = "0.8.0", default-features = false } ndk = { version = "0.9.0", default-features = false }
ndk-sys = "0.5.0"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] # AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
core-foundation = "0.9.3" core-foundation = "0.9.3"
objc2 = "0.5.0" objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1" core-graphics = "0.23.1"
block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.icrate] objc2-foundation = { version = "0.2.2", features = [
version = "0.1.0" "block2",
features = [
"dispatch", "dispatch",
"Foundation", "NSArray",
"Foundation_NSArray", "NSAttributedString",
"Foundation_NSAttributedString", "NSData",
"Foundation_NSMutableAttributedString", "NSDictionary",
"Foundation_NSData", "NSDistributedNotificationCenter",
"Foundation_NSDictionary", "NSEnumerator",
"Foundation_NSString", "NSKeyValueObserving",
"Foundation_NSProcessInfo", "NSNotification",
"Foundation_NSThread", "NSObjCRuntime",
"Foundation_NSNumber", "NSPathUtilities",
"AppKit", "NSProcessInfo",
"AppKit_NSAppearance", "NSRunLoop",
"AppKit_NSApplication", "NSString",
"AppKit_NSBitmapImageRep", "NSThread",
"AppKit_NSButton", "NSValue",
"AppKit_NSColor", ] }
"AppKit_NSControl", objc2-app-kit = { version = "0.2.2", features = [
"AppKit_NSCursor", "NSAppearance",
"AppKit_NSEvent", "NSApplication",
"AppKit_NSGraphicsContext", "NSBitmapImageRep",
"AppKit_NSImage", "NSButton",
"AppKit_NSImageRep", "NSColor",
"AppKit_NSMenu", "NSControl",
"AppKit_NSMenuItem", "NSCursor",
"AppKit_NSPasteboard", "NSDragging",
"AppKit_NSResponder", "NSEvent",
"AppKit_NSScreen", "NSGraphics",
"AppKit_NSTextInputContext", "NSGraphicsContext",
"AppKit_NSView", "NSImage",
"AppKit_NSWindow", "NSImageRep",
"AppKit_NSWindowTabGroup", "NSMenu",
] "NSMenuItem",
"NSOpenGLView",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
[target.'cfg(target_os = "ios")'.dependencies.icrate] # UIKit
version = "0.1.0" [target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
features = [ objc2-foundation = { version = "0.2.2", features = [
"dispatch", "dispatch",
"Foundation", "NSArray",
"Foundation_NSArray", "NSEnumerator",
"Foundation_NSString", "NSGeometry",
"Foundation_NSProcessInfo", "NSObjCRuntime",
"Foundation_NSThread", "NSString",
"Foundation_NSSet", "NSProcessInfo",
] "NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.2.2", features = [
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
"UITouch",
"UITraitCollection",
"UIView",
"UIViewController",
"UIWindow",
] }
# Windows
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1" unicode-segmentation = "1.7.1"
windows-sys = { version = "0.52.0", features = [
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.48"
features = [
"Win32_Devices_HumanInterfaceDevice", "Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation", "Win32_Foundation",
"Win32_Globalization", "Win32_Globalization",
@@ -172,37 +226,59 @@ features = [
"Win32_UI_Shell", "Win32_UI_Shell",
"Win32_UI_TextServices", "Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging", "Win32_UI_WindowsAndMessaging",
] ] }
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] # Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true } ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.12.3" calloop = "0.12.3"
libc = "0.2.64" libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true } memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true } percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } rustix = { version = "0.38.4", default-features = false, features = [
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true } "std",
sctk-adwaita = { version = "0.8.0", default_features = false, optional = true } "system",
wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true } "thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.9.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.0", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.1", optional = true } wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true } wayland-protocols = { version = "0.31.0", features = [
wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } "staging",
], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", 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" xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false } orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1" redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys] # Web
package = "web-sys" [target.'cfg(target_family = "wasm")'.dependencies]
version = "0.3.64" web_sys = { package = "web-sys", version = "0.3.64", features = [
features = [
'AbortController', 'AbortController',
'AbortSignal', 'AbortSignal',
'Blob', 'Blob',
'BlobPropertyBag',
'console', 'console',
'CssStyleDeclaration', 'CssStyleDeclaration',
'Document', 'Document',
@@ -238,10 +314,9 @@ features = [
'VisibilityState', 'VisibilityState',
'Window', 'Window',
'WheelEvent', 'WheelEvent',
'Worker',
'Url', 'Url',
] ] }
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.64" js-sys = "0.3.64"
pin-project = "1" pin-project = "1"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
@@ -253,8 +328,8 @@ atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false } concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies] [target.'cfg(target_family = "wasm")'.dev-dependencies]
console_log = "1" console_error_panic_hook = "0.1"
web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] } tracing-web = "0.1"
[[example]] [[example]]
doc-scrape-examples = true doc-scrape-examples = true
@@ -262,24 +337,14 @@ name = "window"
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = ["dpi"]
"dpi",
"it/common-tests",
"it/gui-test",
"it/gui-test-runner",
"run-wasm",
]
[workspace.package] [workspace.package]
rust-version = "1.70.0" rust-version = "1.73"
repository = "https://github.com/rust-windowing/winit" repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0" license = "Apache-2.0"
edition = "2021" edition = "2021"
[workspace.dependencies] [workspace.dependencies]
async-io = "2.3.1"
gui-test = { path = "it/gui-test" }
mint = "0.5.6"
serde = { version = "1", features = ["serde_derive"] } serde = { version = "1", features = ["serde_derive"] }
serde_json = "1.0.114" mint = "0.5.6"
winit = { path = "." }

View File

@@ -154,7 +154,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility * Home indicator visibility
* Status bar visibility and style * Status bar visibility and style
* Deferring system gestures * Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode * Getting the preferred video mode
### Web ### Web
@@ -179,7 +178,7 @@ Legend:
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ | |Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**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 transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ | |Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |

View File

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

View File

@@ -1,19 +1,18 @@
use cfg_aliases::cfg_aliases; use cfg_aliases::cfg_aliases;
fn main() { 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"); println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases // Setup cfg aliases.
cfg_aliases! { cfg_aliases! {
// Systems. // Systems.
android_platform: { target_os = "android" }, android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") }, web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" }, macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" }, ios_platform: { all(target_vendor = "apple", not(target_os = "macos")) },
windows_platform: { target_os = "windows" }, windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") }, free_unix: { all(unix, not(target_vendor = "apple"), not(android_platform), not(target_os = "emscripten")) },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" }, redox: { target_os = "redox" },
// Native displays. // Native displays.
@@ -21,4 +20,7 @@ fn main() {
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) }, wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
orbital_platform: { redox }, orbital_platform: { redox },
} }
// Winit defined cfgs.
println!("cargo:rustc-check-cfg=cfg(unreleased_changelogs)");
} }

View File

@@ -10,6 +10,8 @@ disallowed-methods = [
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { 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::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 = "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 = "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 = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" }, { path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "core_foundation::runloop::kCFRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" },
{ path = "objc2_foundation::NSRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" }
] ]

View File

@@ -1,40 +0,0 @@
# syntax=docker/dockerfile:1
# Copyright 2024 The Winit Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
ARG DISTRO=ubuntu
ARG DISTRO_VERSION=22.04
FROM "${DISTRO}":"${DISTRO_VERSION}"
SHELL ["/bin/bash", "-eEuxo", "pipefail", "-c"]
ARG DEBIAN_FRONTEND=noninteractive
RUN \
apt-get -o Acquire::Retries=10 -qq update && \
apt-get -o Acquire::Retries=10 -o Dpkg::Use-Pty=0 install -y --no-install-recommends \
cargo \
ca-certificates \
libx11-dev \
libxcursor-dev \
libxcb1-dev \
libxi-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \
xvfb && \
rm -rf \
/var/lib/apt/lists/* \
/var/cache/* \
/var/log/* \
/usr/share/{doc,man}

View File

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

View File

@@ -1,20 +1,77 @@
#[cfg(all( #[cfg(all(feature = "rwh_06", any(x11_platform, macos_platform, windows_platform)))]
feature = "rwh_06",
any(x11_platform, macos_platform, windows_platform)
))]
#[allow(deprecated)] #[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap; use std::collections::HashMap;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle; use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::Window; use winit::window::{Window, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
}
impl ApplicationHandler for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
}
}
}
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window { fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap(); let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes() let mut window_attributes = Window::default_attributes()
@@ -28,67 +85,15 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.create_window(window_attributes).unwrap() event_loop.create_window(window_attributes).unwrap()
} }
let mut windows = HashMap::new(); let event_loop = EventLoop::new().unwrap();
let mut app = Application::default();
let event_loop: EventLoop<()> = EventLoop::new().unwrap(); event_loop.run_app(&mut app)
let mut parent_window_id = None;
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
}
Event::WindowEvent { window_id, event } => match event {
WindowEvent::CloseRequested => {
windows.clear();
event_loop.exit();
}
WindowEvent::CursorEntered { device_id: _ } => {
// On x11, println when the cursor entered in a window even if the child window is created
// by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the parent window,
// so we also can see this log when we move the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
..
},
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
}
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
fill::fill_window(window);
}
}
_ => (),
},
_ => (),
}
})
} }
#[cfg(all( #[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]
feature = "rwh_06",
not(any(x11_platform, macos_platform, windows_platform))
))]
fn main() { 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))] #[cfg(not(web_platform))]
use std::time; use std::time;
use ::tracing::{info, warn};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
@@ -15,6 +16,8 @@ use winit::window::{Window, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_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> { 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."); tracing::init();
println!("Press '2' to switch to WaitUntil mode.");
println!("Press '3' to switch to Poll mode."); info!("Press '1' to switch to Wait mode.");
println!("Press 'R' to toggle request_redraw() calls."); info!("Press '2' to switch to WaitUntil mode.");
println!("Press 'Esc' to close the window."); 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(); let event_loop = EventLoop::new().unwrap();
@@ -53,7 +59,7 @@ struct ControlFlowDemo {
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) { fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
println!("new_events: {cause:?}"); info!("new_events: {cause:?}");
self.wait_cancelled = match cause { self.wait_cancelled = match cause {
StartCause::WaitCancelled { .. } => self.mode == Mode::WaitUntil, StartCause::WaitCancelled { .. } => self.mode == Mode::WaitUntil,
@@ -74,49 +80,44 @@ impl ApplicationHandler for ControlFlowDemo {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
println!("{event:?}"); info!("{event:?}");
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
self.close_requested = true; self.close_requested = true;
} },
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. },
KeyEvent {
logical_key: key,
state: ElementState::Pressed,
..
},
.. ..
} => match key.as_ref() { } => match key.as_ref() {
// WARNING: Consider using `key_without_modifiers()` if available on your platform. // WARNING: Consider using `key_without_modifiers()` if available on your platform.
// See the `key_binding` example // See the `key_binding` example
Key::Character("1") => { Key::Character("1") => {
self.mode = Mode::Wait; self.mode = Mode::Wait;
println!("\nmode: {:?}\n", self.mode); warn!("mode: {:?}", self.mode);
} },
Key::Character("2") => { Key::Character("2") => {
self.mode = Mode::WaitUntil; self.mode = Mode::WaitUntil;
println!("\nmode: {:?}\n", self.mode); warn!("mode: {:?}", self.mode);
} },
Key::Character("3") => { Key::Character("3") => {
self.mode = Mode::Poll; self.mode = Mode::Poll;
println!("\nmode: {:?}\n", self.mode); warn!("mode: {:?}", self.mode);
} },
Key::Character("r") => { Key::Character("r") => {
self.request_redraw = !self.request_redraw; self.request_redraw = !self.request_redraw;
println!("\nrequest_redraw: {}\n", self.request_redraw); warn!("request_redraw: {}", self.request_redraw);
} },
Key::Named(NamedKey::Escape) => { Key::Named(NamedKey::Escape) => {
self.close_requested = true; self.close_requested = true;
} },
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap(); let window = self.window.as_ref().unwrap();
window.pre_present_notify(); window.pre_present_notify();
fill::fill_window(window); fill::fill_window(window);
} },
_ => (), _ => (),
} }
} }
@@ -133,11 +134,11 @@ impl ApplicationHandler for ControlFlowDemo {
event_loop event_loop
.set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)); .set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME));
} }
} },
Mode::Poll => { Mode::Poll => {
thread::sleep(POLL_SLEEP_TIME); thread::sleep(POLL_SLEEP_TIME);
event_loop.set_control_flow(ControlFlow::Poll); event_loop.set_control_flow(ControlFlow::Poll);
} },
}; };
if self.close_requested { if self.close_requested {

View File

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

View File

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

View File

@@ -15,12 +15,12 @@ pub use platform::fill_window;
mod platform { mod platform {
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use softbuffer::{Context, Surface}; use softbuffer::{Context, Surface};
use winit::window::Window; use winit::window::{Window, WindowId};
use winit::window::WindowId;
thread_local! { thread_local! {
// NOTE: You should never do things like that, create context and drop it before // 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. /// The graphics context used to draw to a window.
struct GraphicsContext { struct GraphicsContext {
/// The global softbuffer context. /// The global softbuffer context.
context: Context, context: RefCell<Context<&'static Window>>,
/// The hash map of window IDs to surfaces. /// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface>, surfaces: HashMap<WindowId, Surface<&'static Window, &'static Window>>,
} }
impl GraphicsContext { impl GraphicsContext {
fn new(w: &Window) -> Self { fn new(w: &Window) -> Self {
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(), 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(|| { self.surfaces.entry(window.id()).or_insert_with(|| {
unsafe { Surface::new(&self.context, window) } Surface::new(&self.context.borrow(), unsafe {
.expect("Failed to create a softbuffer surface") 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. // Either get the last context used or create a new one.
let mut gc = gc.borrow_mut(); let mut gc = gc.borrow_mut();
let surface = gc let surface =
.get_or_insert_with(|| GraphicsContext::new(window)) gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
.create_surface(window);
// Fill a buffer with a solid color. // Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xFF181818; const DARK_GRAY: u32 = 0xff181818;
surface surface.resize(width, height).expect("Failed to resize the softbuffer surface");
.resize(width, height)
.expect("Failed to resize the softbuffer surface");
let mut buffer = surface let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
.buffer_mut()
.expect("Failed to get the softbuffer buffer");
buffer.fill(DARK_GRAY); buffer.fill(DARK_GRAY);
buffer buffer.present().expect("Failed to present the softbuffer 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::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fmt;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::path::Path; use std::sync::Arc;
use std::{fmt, mem};
use ::tracing::{error, info};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
use rwh_05::HasRawDisplayHandle; use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
use softbuffer::{Context, Surface}; use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, Ime, WindowEvent}; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event::{MouseButton, MouseScrollDelta};
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState}; use winit::keyboard::{Key, ModifiersState};
use winit::window::{ use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection, Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Theme, Window, WindowId,
}; };
use winit::window::{Window, WindowId};
#[cfg(macos_platform)] #[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS}; use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
@@ -33,11 +32,19 @@ use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify, self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
}; };
#[path = "util/tracing.rs"]
mod tracing;
/// The amount of points to around the window for drag resize direction calculations. /// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.; const BORDER_SIZE: f64 = 20.;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?; #[cfg(web_platform)]
console_error_panic_hook::set_once();
tracing::init();
let event_loop = EventLoop::new()?;
let _event_loop_proxy = event_loop.create_proxy(); let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread. // Wire the user event from another thread.
@@ -45,9 +52,9 @@ fn main() -> Result<(), Box<dyn Error>> {
std::thread::spawn(move || { std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event // Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread. // from a different thread.
println!("Starting to send user event every second"); info!("Starting to send user event every second");
loop { loop {
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp); _event_loop_proxy.wake_up();
std::thread::sleep(std::time::Duration::from_secs(1)); std::thread::sleep(std::time::Duration::from_secs(1));
} }
}); });
@@ -57,12 +64,6 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.run_app(&mut state).map_err(Into::into) event_loop.run_app(&mut state).map_err(Into::into)
} }
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
/// Application state and event handling. /// Application state and event handling.
struct Application { struct Application {
/// Custom cursors assets. /// Custom cursors assets.
@@ -74,24 +75,30 @@ struct Application {
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
context: Option<Context>, context: Option<Context<DisplayHandle<'static>>>,
} }
impl Application { impl Application {
fn new<T>(event_loop: &EventLoop<T>) -> Self { fn new(event_loop: &EventLoop) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe. // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(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 // You'll have to choose an icon size at your own discretion. On X11, the desired size
// by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, // varies by WM, and on Windows, you still have to account for screen scaling. Here
// since it seems to work well enough in most cases. Be careful about going too high, or // we use 32px, since it seems to work well enough in most cases. Be careful about
// you'll be bitten by the low-quality downscaling built into the WM. // going too high, or you'll be bitten by the low-quality downscaling built into the
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/data/icon.png"); // WM.
let icon = load_icon(include_bytes!("data/icon.png"));
let icon = load_icon(Path::new(path)); info!("Loading cursor assets");
println!("Loading cursor assets");
let custom_cursors = vec![ 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/cross.png"))),
event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))), event_loop.create_custom_cursor(decode_cursor(include_bytes!("data/cross2.png"))),
@@ -123,7 +130,7 @@ impl Application {
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() { if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_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); window_attributes = window_attributes.with_activation_token(token);
} }
@@ -132,6 +139,12 @@ impl Application {
window_attributes = window_attributes.with_tabbing_identifier(&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)?; let window = event_loop.create_window(window_attributes)?;
#[cfg(ios_platform)] #[cfg(ios_platform)]
@@ -140,11 +153,12 @@ impl Application {
window.recognize_doubletap_gesture(true); window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true); window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true); window.recognize_rotation_gesture(true);
window.recognize_pan_gesture(true, 2, 2);
} }
let window_state = WindowState::new(self, window)?; let window_state = WindowState::new(self, window)?;
let window_id = window_state.window.id(); 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); self.windows.insert(window_id, window_state);
Ok(window_id) Ok(window_id)
} }
@@ -152,23 +166,23 @@ impl Application {
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) { fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
// let cursor_position = self.cursor_position; // let cursor_position = self.cursor_position;
let window = self.windows.get_mut(&window_id).unwrap(); let window = self.windows.get_mut(&window_id).unwrap();
println!("Executing action: {action:?}"); info!("Executing action: {action:?}");
match action { match action {
Action::CloseWindow => { Action::CloseWindow => {
let _ = self.windows.remove(&window_id); let _ = self.windows.remove(&window_id);
} },
Action::CreateNewWindow => { Action::CreateNewWindow => {
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
if let Err(err) = window.window.request_activation_token() { if let Err(err) = window.window.request_activation_token() {
println!("Failed to get activation token: {err}"); info!("Failed to get activation token: {err}");
} else { } else {
return; return;
} }
if let Err(err) = self.create_window(event_loop, None) { 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::ToggleResizeIncrements => window.toggle_resize_increments(),
Action::ToggleCursorVisibility => window.toggle_cursor_visibility(), Action::ToggleCursorVisibility => window.toggle_cursor_visibility(),
Action::ToggleResizable => window.toggle_resizable(), Action::ToggleResizable => window.toggle_resizable(),
@@ -179,6 +193,12 @@ impl Application {
Action::Minimize => window.minimize(), Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(), Action::NextCursor => window.next_cursor(),
Action::NextCustomCursor => window.next_custom_cursor(&self.custom_cursors), 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::CycleCursorGrab => window.cycle_cursor_grab(),
Action::DragWindow => window.drag_window(), Action::DragWindow => window.drag_window(),
Action::DragResizeWindow => window.drag_resize_window(), Action::DragResizeWindow => window.drag_resize_window(),
@@ -186,18 +206,25 @@ impl Application {
Action::PrintHelp => self.print_help(), Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)] #[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(), 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)] #[cfg(macos_platform)]
Action::CreateNewTab => { Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier(); let tab_id = window.window.tabbing_identifier();
if let Err(err) = self.create_window(event_loop, Some(tab_id)) { 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) { fn dump_monitors(&self, event_loop: &ActiveEventLoop) {
println!("Monitors information"); info!("Monitors information");
let primary_monitor = event_loop.primary_monitor(); let primary_monitor = event_loop.primary_monitor();
for monitor in event_loop.available_monitors() { for monitor in event_loop.available_monitors() {
let intro = if primary_monitor.as_ref() == Some(&monitor) { let intro = if primary_monitor.as_ref() == Some(&monitor) {
@@ -207,60 +234,54 @@ impl Application {
}; };
if let Some(name) = monitor.name() { if let Some(name) = monitor.name() {
println!("{intro}: {name}"); info!("{intro}: {name}");
} else { } else {
println!("{intro}: [no name]"); info!("{intro}: [no name]");
} }
let PhysicalSize { width, height } = monitor.size(); let PhysicalSize { width, height } = monitor.size();
print!(" Current mode: {width}x{height}"); info!(
if let Some(m_hz) = monitor.refresh_rate_millihertz() { " Current mode: {width}x{height}{}",
println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000); if let Some(m_hz) = monitor.refresh_rate_millihertz() {
} else { format!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000)
println!(); } else {
} String::new()
}
);
let PhysicalPosition { x, y } = monitor.position(); 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() { for mode in monitor.video_modes() {
let PhysicalSize { width, height } = mode.size(); let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth(); let bits = mode.bit_depth();
let m_hz = mode.refresh_rate_millihertz(); let m_hz = mode.refresh_rate_millihertz();
println!( info!(" {width}x{height}x{bits} @ {}.{} Hz", m_hz / 1000, m_hz % 1000);
" {width}x{height}x{bits} @ {}.{} Hz",
m_hz / 1000,
m_hz % 1000
);
} }
} }
} }
/// Process the key binding. /// Process the key binding.
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> { fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
KEY_BINDINGS.iter().find_map(|binding| { KEY_BINDINGS
binding .iter()
.is_triggered_by(&key, mods) .find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action))
.then_some(binding.action)
})
} }
/// Process mouse binding. /// Process mouse binding.
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> { fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
MOUSE_BINDINGS.iter().find_map(|binding| { MOUSE_BINDINGS
binding .iter()
.is_triggered_by(&button, mods) .find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action))
.then_some(binding.action)
})
} }
fn print_help(&self) { fn print_help(&self) {
println!("Keyboard bindings:"); info!("Keyboard bindings:");
for binding in KEY_BINDINGS { for binding in KEY_BINDINGS {
println!( info!(
"{}{:<10} - {} ({})", "{}{:<10} - {} ({})",
modifiers_to_string(binding.mods), modifiers_to_string(binding.mods),
binding.trigger, binding.trigger,
@@ -268,9 +289,9 @@ impl Application {
binding.action.help(), binding.action.help(),
); );
} }
println!("Mouse bindings:"); info!("Mouse bindings:");
for binding in MOUSE_BINDINGS { for binding in MOUSE_BINDINGS {
println!( info!(
"{}{:<10} - {} ({})", "{}{:<10} - {} ({})",
modifiers_to_string(binding.mods), modifiers_to_string(binding.mods),
mouse_button_to_string(binding.trigger), mouse_button_to_string(binding.trigger),
@@ -281,9 +302,9 @@ impl Application {
} }
} }
impl ApplicationHandler<UserEvent> for Application { impl ApplicationHandler for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) { fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
println!("User event: {event:?}"); info!("User wake up");
} }
fn window_event( fn window_event(
@@ -300,50 +321,46 @@ impl ApplicationHandler<UserEvent> for Application {
match event { match event {
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
window.resize(size); window.resize(size);
} },
WindowEvent::Focused(focused) => { WindowEvent::Focused(focused) => {
if focused { if focused {
println!("Window={window_id:?} fosused"); info!("Window={window_id:?} focused");
} else { } else {
println!("Window={window_id:?} unfosused"); info!("Window={window_id:?} unfocused");
}
}
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);
} }
}, },
WindowEvent::KeyboardInput { WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
event, info!("Window={window_id:?} changed scale to {scale_factor}");
is_synthetic: false, },
.. 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; let mods = window.modifiers;
// Dispatch actions only on press. // Dispatch actions only on press.
@@ -358,65 +375,68 @@ impl ApplicationHandler<UserEvent> for Application {
self.handle_action(event_loop, window_id, action); self.handle_action(event_loop, window_id, action);
} }
} }
} },
WindowEvent::MouseInput { button, state, .. } => { WindowEvent::MouseInput { button, state, .. } => {
let mods = window.modifiers; let mods = window.modifiers;
if let Some(action) = state if let Some(action) =
.is_pressed() state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
.then(|| Self::process_mouse_binding(button, &mods))
.flatten()
{ {
self.handle_action(event_loop, window_id, action); self.handle_action(event_loop, window_id, action);
} }
} },
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
println!("Cursor left Window={window_id:?}"); info!("Cursor left Window={window_id:?}");
window.cursor_left(); window.cursor_left();
} },
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
println!("Moved cursor to {position:?}"); info!("Moved cursor to {position:?}");
window.cursor_moved(position); window.cursor_moved(position);
} },
WindowEvent::ActivationTokenDone { token: _token, .. } => { WindowEvent::ActivationTokenDone { token: _token, .. } => {
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
{ {
startup_notify::set_activation_token_env(_token); startup_notify::set_activation_token_env(_token);
if let Err(err) = self.create_window(event_loop, None) { 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 { 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) => { Ime::Preedit(text, caret_pos) => {
println!("Preedit: {}, with caret at {:?}", text, caret_pos); info!("Preedit: {}, with caret at {:?}", text, caret_pos);
} },
Ime::Commit(text) => { Ime::Commit(text) => {
println!("Committed: {}", text); info!("Committed: {}", text);
} },
Ime::Disabled => println!("IME disabled for Window={window_id:?}"), Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
}, },
WindowEvent::PinchGesture { delta, .. } => { WindowEvent::PinchGesture { delta, .. } => {
window.zoom += delta; window.zoom += delta;
let zoom = window.zoom; let zoom = window.zoom;
if delta > 0.0 { if delta > 0.0 {
println!("Zoomed in {delta:.5} (now: {zoom:.5})"); info!("Zoomed in {delta:.5} (now: {zoom:.5})");
} else { } else {
println!("Zoomed out {delta:.5} (now: {zoom:.5})"); info!("Zoomed out {delta:.5} (now: {zoom:.5})");
} }
} },
WindowEvent::RotationGesture { delta, .. } => { WindowEvent::RotationGesture { delta, .. } => {
window.rotated += delta; window.rotated += delta;
let rotated = window.rotated; let rotated = window.rotated;
if delta > 0.0 { if delta > 0.0 {
println!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})"); info!("Rotated counterclockwise {delta:.5} (now: {rotated:.5})");
} else { } 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 { .. } => { WindowEvent::DoubleTapGesture { .. } => {
println!("Smart zoom"); info!("Smart zoom");
} },
WindowEvent::TouchpadPressure { .. } WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled | WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. } | WindowEvent::KeyboardInput { .. }
@@ -436,23 +456,22 @@ impl ApplicationHandler<UserEvent> for Application {
device_id: DeviceId, device_id: DeviceId,
event: DeviceEvent, event: DeviceEvent,
) { ) {
println!("Device {device_id:?} event: {event:?}"); info!("Device {device_id:?} event: {event:?}");
} }
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
println!("Resumed the event loop"); info!("Resumed the event loop");
self.dump_monitors(event_loop); self.dump_monitors(event_loop);
// Create initial window. // Create initial window.
self.create_window(event_loop, None) self.create_window(event_loop, None).expect("failed to create initial window");
.expect("failed to create initial window");
self.print_help(); self.print_help();
} }
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() { if self.windows.is_empty() {
println!("No windows left, exiting..."); info!("No windows left, exiting...");
event_loop.exit(); event_loop.exit();
} }
} }
@@ -472,9 +491,9 @@ struct WindowState {
/// ///
/// NOTE: This surface must be dropped before the `Window`. /// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
surface: Surface, surface: Surface<DisplayHandle<'static>, Arc<Window>>,
/// The actual winit Window. /// The actual winit Window.
window: Window, window: Arc<Window>,
/// The window theme we're drawing with. /// The window theme we're drawing with.
theme: Theme, theme: Theme,
/// Cursor position over the window. /// Cursor position over the window.
@@ -489,6 +508,8 @@ struct WindowState {
zoom: f64, zoom: f64,
/// The amount of rotation of the window. /// The amount of rotation of the window.
rotated: f32, rotated: f32,
/// The amount of pan of the window.
panned: PhysicalPosition<f32>,
#[cfg(macos_platform)] #[cfg(macos_platform)]
option_as_alt: OptionAsAlt, option_as_alt: OptionAsAlt,
@@ -501,13 +522,15 @@ struct WindowState {
impl WindowState { impl WindowState {
fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> { 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 // SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it. // it doesn't outlive it.
#[cfg(not(any(android_platform, ios_platform)))] #[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); let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}"); info!("Theme: {theme:?}");
let named_idx = 0; let named_idx = 0;
window.set_cursor(CURSORS[named_idx]); window.set_cursor(CURSORS[named_idx]);
@@ -532,6 +555,7 @@ impl WindowState {
modifiers: Default::default(), modifiers: Default::default(),
occluded: Default::default(), occluded: Default::default(),
rotated: Default::default(), rotated: Default::default(),
panned: Default::default(),
zoom: Default::default(), zoom: Default::default(),
}; };
@@ -543,8 +567,7 @@ impl WindowState {
self.ime = !self.ime; self.ime = !self.ime;
self.window.set_ime_allowed(self.ime); self.window.set_ime_allowed(self.ime);
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() { if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
self.window self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
} }
} }
@@ -555,8 +578,7 @@ impl WindowState {
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) { pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
self.cursor_position = Some(position); self.cursor_position = Some(position);
if self.ime { if self.ime {
self.window self.window.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
.set_ime_cursor_area(position, PhysicalSize::new(20, 20));
} }
} }
@@ -594,7 +616,7 @@ impl WindowState {
Some(_) => None, Some(_) => None,
None => Some(LogicalSize::new(25.0, 25.0)), 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); self.window.set_resize_increments(new_increments);
} }
@@ -616,9 +638,9 @@ impl WindowState {
CursorGrabMode::Confined => CursorGrabMode::Locked, CursorGrabMode::Confined => CursorGrabMode::Locked,
CursorGrabMode::Locked => CursorGrabMode::None, 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) { 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 +652,34 @@ impl WindowState {
OptionAsAlt::OnlyRight => OptionAsAlt::Both, OptionAsAlt::OnlyRight => OptionAsAlt::Both,
OptionAsAlt::Both => OptionAsAlt::None, 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); 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. /// Pick the next cursor.
fn next_cursor(&mut self) { fn next_cursor(&mut self) {
self.named_idx = (self.named_idx + 1) % CURSORS.len(); self.named_idx = (self.named_idx + 1) % CURSORS.len();
println!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]); info!("Setting cursor to \"{:?}\"", CURSORS[self.named_idx]);
self.window self.window.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
.set_cursor(Cursor::Icon(CURSORS[self.named_idx]));
} }
/// Pick the next custom cursor. /// Pick the next custom cursor.
@@ -649,24 +689,52 @@ impl WindowState {
self.window.set_cursor(cursor); 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. /// 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)))] #[cfg(not(any(android_platform, ios_platform)))]
{ {
let (width, height) = let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
match (NonZeroU32::new(_size.width), NonZeroU32::new(_size.height)) { {
(Some(width), Some(height)) => (width, height), (Some(width), Some(height)) => (width, height),
_ => return, _ => return,
}; };
self.surface self.surface.resize(width, height).expect("failed to resize inner buffer");
.resize(width, height)
.expect("failed to resize inner buffer");
} }
self.window.request_redraw(); self.window.request_redraw();
} }
/// Change the theme. /// Change the theme that things are drawn in.
fn set_theme(&mut self, theme: Theme) { fn set_draw_theme(&mut self, theme: Theme) {
self.theme = theme; self.theme = theme;
self.window.request_redraw(); self.window.request_redraw();
} }
@@ -681,9 +749,9 @@ impl WindowState {
/// Drag the window. /// Drag the window.
fn drag_window(&self) { fn drag_window(&self) {
if let Err(err) = self.window.drag_window() { if let Err(err) = self.window.drag_window() {
println!("Error starting window drag: {err}"); info!("Error starting window drag: {err}");
} else { } else {
println!("Dragging window Window={:?}", self.window.id()); info!("Dragging window Window={:?}", self.window.id());
} }
} }
@@ -692,9 +760,9 @@ impl WindowState {
let position = match self.cursor_position { let position = match self.cursor_position {
Some(position) => position, Some(position) => position,
None => { None => {
println!("Drag-resize requires cursor to be inside the window"); info!("Drag-resize requires cursor to be inside the window");
return; return;
} },
}; };
let win_size = self.window.inner_size(); let win_size = self.window.inner_size();
@@ -731,9 +799,9 @@ impl WindowState {
}; };
if let Err(err) = self.window.drag_resize_window(direction) { 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 { } else {
println!("Drag-resizing window Window={:?}", self.window.id()); info!("Drag-resizing window Window={:?}", self.window.id());
} }
} }
@@ -749,12 +817,12 @@ impl WindowState {
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> { fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded { if self.occluded {
println!("Skipping drawing occluded window={:?}", self.window.id()); info!("Skipping drawing occluded window={:?}", self.window.id());
return Ok(()); return Ok(());
} }
const WHITE: u32 = 0xFFFFFFFF; const WHITE: u32 = 0xffffffff;
const DARK_GRAY: u32 = 0xFF181818; const DARK_GRAY: u32 = 0xff181818;
let color = match self.theme { let color = match self.theme {
Theme::Light => WHITE, Theme::Light => WHITE,
@@ -770,7 +838,7 @@ impl WindowState {
#[cfg(any(android_platform, ios_platform))] #[cfg(any(android_platform, ios_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> { fn draw(&mut self) -> Result<(), Box<dyn Error>> {
println!("Drawing but without rendering..."); info!("Drawing but without rendering...");
Ok(()) Ok(())
} }
} }
@@ -783,11 +851,7 @@ struct Binding<T: Eq> {
impl<T: Eq> Binding<T> { impl<T: Eq> Binding<T> {
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self { const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
Self { Self { trigger, mods, action }
trigger,
mods,
action,
}
} }
fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool { fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
@@ -809,6 +873,10 @@ enum Action {
Minimize, Minimize,
NextCursor, NextCursor,
NextCustomCursor, NextCustomCursor,
#[cfg(web_platform)]
UrlCustomCursor,
#[cfg(web_platform)]
AnimationCustomCursor,
CycleCursorGrab, CycleCursorGrab,
PrintHelp, PrintHelp,
DragWindow, DragWindow,
@@ -816,8 +884,10 @@ enum Action {
ShowWindowMenu, ShowWindowMenu,
#[cfg(macos_platform)] #[cfg(macos_platform)]
CycleOptionAsAlt, CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)] #[cfg(macos_platform)]
CreateNewTab, CreateNewTab,
RequestResize,
} }
impl Action { impl Action {
@@ -835,6 +905,10 @@ impl Action {
Action::ToggleResizeIncrements => "Use resize increments when resizing window", Action::ToggleResizeIncrements => "Use resize increments when resizing window",
Action::NextCursor => "Advance the cursor to the next value", Action::NextCursor => "Advance the cursor to the next value",
Action::NextCustomCursor => "Advance custom 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::CycleCursorGrab => "Cycle through cursor grab mode",
Action::PrintHelp => "Print help", Action::PrintHelp => "Print help",
Action::DragWindow => "Start window drag", Action::DragWindow => "Start window drag",
@@ -842,8 +916,12 @@ impl Action {
Action::ShowWindowMenu => "Show window menu", Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)] #[cfg(macos_platform)]
Action::CycleOptionAsAlt => "Cycle option as alt mode", 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)] #[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab", Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
} }
} }
} }
@@ -862,11 +940,24 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap() 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 (icon_rgba, icon_width, icon_height) = {
let image = image::open(path) let image = image::load_from_memory(bytes).unwrap().into_rgba8();
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions(); let (width, height) = image.dimensions();
let rgba = image.into_raw(); let rgba = image.into_raw();
(rgba, width, height) (rgba, width, height)
@@ -950,6 +1041,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab), Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
// M. // M.
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize), Binding::new("M", ModifiersState::ALT, Action::Minimize),
@@ -958,7 +1050,23 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
// C. // C.
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor), Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor), 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), 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)] #[cfg(macos_platform)]
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab), Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)] #[cfg(macos_platform)]
@@ -966,19 +1074,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
]; ];
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[ const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
Binding::new( Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow),
MouseButton::Left, Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow),
ModifiersState::ALT, Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu),
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 => { WindowEvent::RedrawRequested => {
window.pre_present_notify(); window.pre_present_notify();
fill::fill_window(window); fill::fill_window(window);
} },
_ => (), _ => (),
} }
} }
@@ -58,10 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let mut app = XEmbedDemo { let mut app = XEmbedDemo { parent_window_id, window: None };
parent_window_id,
window: None,
};
event_loop.run_app(&mut app).map_err(Into::into) event_loop.run_app(&mut app).map_err(Into::into)
} }

View File

@@ -1,12 +0,0 @@
[package]
name = "common-tests"
version = "0.1.0"
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[dependencies]
gui-test.workspace = true
macro_rules_attribute = "0.2.0"
winit.workspace = true

View File

@@ -1,23 +0,0 @@
//! Run the test.
use gui_test::{test, Harness};
use macro_rules_attribute::apply;
use winit::event_loop::EventLoop;
#[allow(deprecated)]
#[apply(test)]
fn initialize(harness: &mut Harness) {
let mut group = harness.group("sanity");
group.harness().with_test("startup/shutdown", || {
let evl = EventLoop::new().expect("initialization");
evl.run(|_event, elwt| {
elwt.exit();
})
.expect("running");
});
}
gui_test::main! {
gui_test::remote::handler()
}

View File

@@ -1,13 +0,0 @@
[package]
name = "gui-test-runner"
version = "0.1.0"
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[dependencies]
camino = "1.1.6"
fastrand = "2.0.1"
gui-test.workspace = true
serde_json.workspace = true

View File

@@ -1,81 +0,0 @@
// Copyright 2024 The Winit Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A wrapper around the `Command` type that dumps the command to stderr.
//!
//! Essentially it's like `set -x` in Bash.
use std::ffi::{OsStr, OsString};
use std::io::{self, prelude::*};
use std::process::Child;
/// Simple `Command` wrapper.
pub(super) struct Command {
/// Actual inner command.
inner: std::process::Command,
/// Command to run.
text: Vec<OsString>,
}
impl Command {
/// Create a new `Command`.
pub(super) fn new(cmd: impl AsRef<OsStr>) -> Self {
let cmd = cmd.as_ref();
Self {
inner: std::process::Command::new(cmd),
text: vec![cmd.to_os_string()],
}
}
/// Add an argument to the `Command`.
pub(super) fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
let arg = arg.as_ref();
self.inner.arg(arg);
self.text.push(arg.to_os_string());
self
}
/// Add multiple arguments to the `Command`.
pub(super) fn args<T: AsRef<OsStr>>(&mut self, args: impl IntoIterator<Item = T>) -> &mut Self {
for arg in args {
let arg = arg.as_ref();
self.inner.arg(arg);
self.text.push(arg.to_os_string());
}
self
}
/// Spawn the process.
pub(super) fn spawn(&mut self) -> io::Result<Child> {
dump_text(&self.text);
self.inner.spawn()
}
}
/// Dump `OsString` list to stderr.
fn dump_text(text: &[OsString]) {
let mut cerr = io::stderr().lock();
write!(&mut cerr, "+").unwrap();
for arg in text {
match arg.to_str() {
Some(arg) => write!(&mut cerr, " {}", arg).unwrap(),
None => write!(&mut cerr, " {:?}", arg).unwrap(),
}
}
writeln!(&mut cerr).unwrap();
}

View File

@@ -1,91 +0,0 @@
// Copyright 2024 The Winit Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Run the actual Docker command.
use crate::command::Command;
use camino::Utf8Path;
use std::ffi::OsStr;
use std::io;
use std::process::Child;
/// The Docker command line.
pub(super) struct DockerRun {
command: Command,
}
impl DockerRun {
/// Start the command.
pub(super) fn new() -> Self {
let mut command = Command::new("docker");
command.arg("run");
Self { command }
}
/// Run with an environment variable.
pub(super) fn env(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
let env_arg = format!("{}={}", name.as_ref(), value.as_ref());
self.command.args(["--env", &env_arg]);
self
}
/// Run with a simple `init` process.
pub(super) fn init(&mut self) -> &mut Self {
self.command.arg("--init");
self
}
/// Set the working directory.
pub(super) fn workdir(&mut self, dir: impl AsRef<OsStr>) -> &mut Self {
self.command.arg("--workdir");
self.command.arg(dir);
self
}
/// Remove the container once it is complete.
pub(super) fn rm(&mut self) -> &mut Self {
self.command.arg("--rm");
self
}
/// Pass a volume into the container.
pub(super) fn volume(
&mut self,
host: impl AsRef<Utf8Path>,
container: impl AsRef<Utf8Path>,
) -> &mut Self {
let list = format!("{}:{}", host.as_ref(), container.as_ref());
self.command.args(["--volume", &list]);
self
}
/// Run the container with a command.
pub(super) fn run_with_command<T: AsRef<OsStr>>(
&mut self,
container_name: impl AsRef<str>,
container_version: impl AsRef<str>,
command: impl IntoIterator<Item = T>,
) -> io::Result<Child> {
self.command.arg(format!(
"{}:{}",
container_name.as_ref(),
container_version.as_ref()
));
self.command.args(command);
self.command.spawn()
}
}

View File

@@ -1,114 +0,0 @@
// Copyright 2024 The Winit Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Run tests inside of a Linux docker container.
use super::command::DockerRun;
use crate::stream::StreamReader;
use gui_test::remote::handler;
use gui_test::TestHandler;
use std::io;
use std::os::unix::net::UnixListener;
use std::path::Path;
use std::thread;
const UBUNTU_DOCKERFILE: &str = "ghcr.io/rust-windowing/testubuntu";
const LATEST: &str = "latest";
/// Run the provided test in a Linux docker container.
pub(crate) fn linux_test(test_name: &str) -> io::Result<()> {
// Create a Unix socket to listen for events on.
let unix_path = format!("/tmp/gui_test_{}.sock", fastrand::u16(..));
let listener = UnixListener::bind(&unix_path)?;
// Spawn the Docker container.
let mut container = {
let mut docker = DockerRun::new();
// Usual options.
docker.rm().init();
// Pass through the socket as a volume.
docker.volume(&unix_path, &unix_path);
// Pass through the winit directory.
let winit_directory = Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.find_map(|path| {
let cargo_toml = path.join("Cargo.toml");
let contents = std::fs::read(cargo_toml).ok()?;
if std::str::from_utf8(&contents)
.ok()?
.contains("name = \"winit\"")
{
Some(path)
} else {
None
}
})
.unwrap();
docker.volume(
camino::Utf8Path::from_path(winit_directory).unwrap(),
"/app/winit/",
);
// Set the working dir to this directory.
docker.workdir("/app/winit/");
// Set GUI_TEST_UNIX_STREAM to the socket.
docker.env("GUI_TEST_UNIX_STREAM", &unix_path);
// Set CARGO_TARGET_DIR to a random other directory.
docker.env("CARGO_TARGET_DIR", "/tmp/");
// The command to run the test.
let command = ["xvfb-run", "cargo", "run", "-p", test_name];
// Spawn the test container.
docker.run_with_command(UBUNTU_DOCKERFILE, LATEST, command)?
};
// Run the console listener in another thread.
let handle = thread::spawn(move || {
// Attach to the listener.
let (event_reader, _) = listener.accept().unwrap();
// Read events and output them as we get them.
let input = StreamReader::new(event_reader);
let mut output = handler();
for event in input {
let event = event?;
output.handle_test(event);
}
io::Result::Ok(())
});
// Wait for the container to finish.
if !container.wait()?.success() {
return Err(io::Error::new(
io::ErrorKind::Other,
"docker exited with a failure exit code",
));
}
// Stop the thread.
handle.join().unwrap().unwrap();
Ok(())
}

View File

@@ -1,20 +0,0 @@
// Copyright 2024 The Winit Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Dealing with Docker.
mod command;
#[cfg(unix)]
pub(super) mod linux;

View File

@@ -1,69 +0,0 @@
//! Runner for the `gui-test` system.
mod command;
mod docker;
mod stream;
use std::env;
use std::process::{Command, Stdio};
fn main() {
let mut args = env::args();
// Get the test crate name.
let test_crate = args.nth(1).unwrap();
// Get the target.
let target_tag = args.next().unwrap();
// Split the target into the target and the tag.
let (target, tag) = {
let mut split = target_tag.splitn(1, ':');
let target = split.next().unwrap();
let tag = split.next();
(target, tag)
};
// Get the current target.
let current_target = current_target();
// If we are building for Linux, run the Linux Docker container.
// TODO: Architecture differences.
if target.contains("linux") {
docker::linux::linux_test(&test_crate).unwrap();
return;
}
// For now, we only support building for the current target.
assert_eq!(target, current_target);
assert!(tag.is_none());
// Just run the crate.
if !Command::new("cargo")
.args(["run", "-p", &test_crate])
.status()
.unwrap()
.success()
{
panic!("test failed");
}
}
/// Get the current target.
fn current_target() -> String {
let output = Command::new("rustc")
.arg("-vV")
.stdout(Stdio::piped())
.output()
.unwrap();
// Look for the line that starts with "host".
let stdout = String::from_utf8(output.stdout).unwrap();
for line in stdout.lines() {
if let Some(host) = line.strip_prefix("host: ") {
return host.to_string();
}
}
panic!("failed to find host: line in rustc output")
}

View File

@@ -1,81 +0,0 @@
// Copyright 2024 The Winit Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Read test events from a stream.
use gui_test::{TestEvent, TestEventType};
use std::io::{self, Read};
/// Read events from a stream.
pub(super) struct StreamReader<R> {
/// The inner reader.
reader: Option<R>,
/// Reused buffer.
buffer: Vec<u8>,
}
impl<R: Read> StreamReader<R> {
/// Create a new stream reader.
pub(super) fn new(reader: R) -> Self {
Self {
reader: Some(reader),
buffer: vec![0u8; 1024],
}
}
}
macro_rules! leap {
($self:expr, $e:expr) => {{
match ($e) {
Ok(x) => x,
Err(err) => {
($self).reader = None;
return Some(Err(err));
}
}
}};
}
impl<R: Read> Iterator for StreamReader<R> {
type Item = io::Result<TestEvent>;
fn next(&mut self) -> Option<Self::Item> {
let reader = self.reader.as_mut()?;
// Read eight bytes from the reader to get payload length.
let mut len_buffer = [0u8; 8];
leap!(self, reader.read_exact(&mut len_buffer));
// Parse that, then read the length's worth of bytes.
let length = u64::from_be_bytes(len_buffer);
self.buffer.resize(length as usize, 0);
leap!(self, reader.read_exact(&mut self.buffer));
// Parse as a test event.
let event: TestEvent = leap!(
self,
serde_json::from_slice(&self.buffer)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
);
// If this is complete, stop running.
if matches!(event.ty, TestEventType::Complete { .. }) {
self.reader = None;
}
// We are okay.
Some(Ok(event))
}
}

View File

@@ -1,17 +0,0 @@
[package]
name = "gui-test"
version = "0.1.0"
rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[dependencies]
async-executor = "1.8.0"
async-io.workspace = true
async-lock = "3.3.0"
async-process = "2.1.0"
inventory = "0.3.15"
owo-colors = "4.0.0"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

View File

@@ -1,443 +0,0 @@
//! A testing framework that can be run remotely.
pub mod remote;
pub mod stream;
pub mod user;
use serde::{Deserialize, Serialize};
use std::env;
use std::ffi::{OsStr, OsString};
use std::mem;
use std::num::NonZeroUsize;
use std::panic;
const GUI_TEST_CURRENT_TEST_NAME: &str = "GUI_TEST_CURRENT_TEST_NAME";
const GUI_TEST_SUBPROCESS_LIMIT: &str = "GUI_TEST_SUBPROCESS_LIMIT";
const DEFAULT_LIMIT: usize = 1;
#[doc(hidden)]
pub use inventory as __inventory;
/// Replacement for the `main` function.
#[macro_export]
macro_rules! main {
($handler:expr) => {
fn main() {
$crate::__entry(|| $handler)
}
};
}
/// Set up a test for the test framework.
#[macro_export]
macro_rules! test {
(
$(#[$attr:meta])*
fn $name:ident ($hname:ident : $htype:ty) $bl:block
) => {
const _: () = {
$(#[$attr])*
fn $name ($hname: $htype) $bl
$crate::__inventory::submit! {
$crate::__TestStart::__new(
stringify!($name),
$name
)
}
};
};
}
/// Test start.
#[doc(hidden)]
pub struct __TestStart {
/// The name of the test.
name: &'static str,
/// The function to call.
func: fn(&mut Harness),
}
impl __TestStart {
/// Create a new test start.
#[doc(hidden)]
pub const fn __new(name: &'static str, func: fn(&mut Harness)) -> Self {
Self { name, func }
}
}
inventory::collect! {
__TestStart
}
/// A harness for running the tests.
pub struct Harness {
/// Name of the test start.
name: String,
/// The inner test handler.
handler: Box<dyn TestHandler + Send + 'static>,
/// Number of tests that have been run so far.
test_count: usize,
/// Number of tests that have failed so far.
test_fails: usize,
/// Number of tests that have succeeded.
test_passed: usize,
/// Current state of the test harness.
state: State,
}
impl Harness {
/// Create a new test harness.
fn new<H: TestHandler + Send + 'static>(name: &str, handler: H) -> Self {
Self {
name: name.to_string(),
handler: Box::new(handler),
test_count: 0,
test_fails: 0,
test_passed: 0,
state: State::Default,
}
}
/// Begin a test.
pub fn test(&mut self, name: impl Into<String>) -> Testing<'_> {
// Make sure we aren't mid test.
match mem::replace(&mut self.state, State::Default) {
State::InTest { past_groups } => {
self.state = State::InTest { past_groups };
panic!("tried to start a test while another was underway");
}
State::InGroups(groups) => {
self.state = State::InTest {
past_groups: Some(groups),
};
}
State::Default => {
self.state = State::InTest { past_groups: None };
}
}
// Send the "test started" event to the handler.
self.send_event(TestEventType::TestStarted { name: name.into() });
// Return the handle.
Testing {
harness: Some(self),
}
}
/// Run a closure as a test.
pub fn with_test<T>(&mut self, name: impl Into<String>, f: impl FnOnce() -> T) -> T {
let test = self.test(name.into());
match panic::catch_unwind(panic::AssertUnwindSafe(f)) {
Ok(x) => x,
Err(err) => {
if let Some(panic) = err.downcast_ref::<&'static str>() {
test.fail(panic.to_string());
} else if let Some(panic) = err.downcast_ref::<String>() {
test.fail(panic.clone());
} else {
test.fail("unintelligible error".to_string());
}
panic::resume_unwind(err)
}
}
}
/// Begin a test group.
pub fn group(&mut self, name: impl Into<String>) -> Grouping<'_> {
// Make sure we can begin a group.
match mem::replace(&mut self.state, State::Default) {
State::Default => {
self.state = State::InGroups(NonZeroUsize::new(1).unwrap());
}
State::InGroups(groups) => {
self.state = State::InGroups(groups.checked_add(1).unwrap());
}
State::InTest { past_groups } => {
self.state = State::InTest { past_groups };
panic!("cannot start group mid-test")
}
}
// Send the "group started" event to the handler.
self.send_event(TestEventType::GroupStarted { name: name.into() });
// Return the handle.
Grouping { harness: self }
}
/// Run a closure inside of a group.
pub fn with_group<T>(&mut self, name: impl Into<String>, f: impl FnOnce(&mut Self) -> T) -> T {
let mut group = self.group(name.into());
f(group.harness())
}
/// End an ongoing test.
fn end_test(&mut self, reason: TestResult) {
self.test_count += 1;
match &reason {
TestResult::Passed => self.test_passed += 1,
TestResult::Failed(..) => self.test_fails += 1,
_ => {}
}
self.send_event(TestEventType::TestEnded { result: reason });
let count = match mem::replace(&mut self.state, State::Default) {
State::InTest { past_groups } => past_groups,
_ => unreachable!(),
};
self.state = match count {
None => State::Default,
Some(count) => State::InGroups(count),
};
}
/// End the current group.
fn end_group(&mut self) {
self.send_event(TestEventType::GroupEnded);
let count = match mem::replace(&mut self.state, State::Default) {
State::InGroups(groups) => groups,
_ => unreachable!(),
};
self.state = match NonZeroUsize::new(count.get() - 1) {
None => State::Default,
Some(groups) => State::InGroups(groups),
};
}
/// Send a test event of the provided type.
fn send_event(&mut self, ty: TestEventType) {
let event = TestEvent {
runner: self.name.clone(),
ty,
};
self.handler.handle_test(event);
}
}
impl Drop for Harness {
fn drop(&mut self) {
self.send_event(TestEventType::Complete {
total: self.test_count,
fail: self.test_fails,
pass: self.test_passed,
});
}
}
/// An in-progress test.
pub struct Testing<'a> {
harness: Option<&'a mut Harness>,
}
impl Testing<'_> {
/// Skip this test.
pub fn skip(mut self) {
// Send the "skipped" event.
self.harness.take().unwrap().end_test(TestResult::Skipped);
}
/// Fail this test.
fn fail(mut self, panic: String) {
self.harness
.take()
.unwrap()
.end_test(TestResult::Failed(panic));
}
}
impl Drop for Testing<'_> {
fn drop(&mut self) {
if let Some(harness) = self.harness.take() {
let result = if std::thread::panicking() {
TestResult::Failed("thread panicked".into())
} else {
TestResult::Passed
};
harness.end_test(result);
}
}
}
/// We are running a test group.
pub struct Grouping<'a> {
harness: &'a mut Harness,
}
impl Grouping<'_> {
/// Get the underlying test harness.
pub fn harness(&mut self) -> &mut Harness {
&mut self.harness
}
}
impl Drop for Grouping<'_> {
fn drop(&mut self) {
self.harness.end_group();
}
}
/// Current testing state.
enum State {
/// We are in the middle of this many groups.
InGroups(NonZeroUsize),
/// We are in the middle of a test.
InTest { past_groups: Option<NonZeroUsize> },
/// We are in the default state.
Default,
}
/// A handler for incoming test events.
pub trait TestHandler {
/// Handle a test.
fn handle_test(&mut self, event: TestEvent);
}
impl<T: TestHandler + ?Sized> TestHandler for Box<T> {
fn handle_test(&mut self, event: TestEvent) {
(**self).handle_test(event)
}
}
/// An event produced by the test harness.
#[derive(Debug, Serialize, Deserialize)]
pub struct TestEvent {
/// The name of the runner associated with the event.
pub runner: String,
/// The type of the event.
pub ty: TestEventType,
}
/// The type of the event.
#[non_exhaustive]
#[derive(Debug, Serialize, Deserialize)]
pub enum TestEventType {
/// The tests are complete and the harness can be disconnected.
Complete {
/// Total number of tests.
total: usize,
/// Total number of passing tests.
pass: usize,
/// Total number of failed tests.
fail: usize,
},
/// A test has started.
TestStarted { name: String },
/// A test has completed.
TestEnded { result: TestResult },
/// A test group has started.
GroupStarted { name: String },
/// A test group has ended.
GroupEnded,
}
/// The result of a test.
#[non_exhaustive]
#[derive(Debug, Serialize, Deserialize)]
pub enum TestResult {
/// The test passed.
Passed,
/// The test failed with the provided error.
Failed(String),
/// The test was skipped.
Skipped,
}
/// Entry point of the test.
#[doc(hidden)]
pub fn __entry<H: TestHandler + Send + 'static>(handler: impl FnOnce() -> H) {
// Look for the test name environment variable.
if let Some(test_name) = env::var(GUI_TEST_CURRENT_TEST_NAME)
.ok()
.filter(|test_name| !test_name.is_empty())
{
// Find the provided test.
let test_to_run = inventory::iter::<__TestStart>
.into_iter()
.find(|test| test.name == test_name)
.unwrap_or_else(|| panic!("unable to find test '{test_name}'"));
// Create a harness.
let mut harness = Harness::new(test_to_run.name, handler());
// Run the test.
panic::catch_unwind(panic::AssertUnwindSafe(move || {
(test_to_run.func)(&mut harness)
}))
.ok();
} else {
// Run a subprocess for every test.
let limit = env::var(GUI_TEST_SUBPROCESS_LIMIT)
.ok()
.and_then(|limit| limit.parse::<usize>().ok())
.unwrap_or(DEFAULT_LIMIT);
let process_name = env::args_os().next().unwrap();
let sema = async_lock::Semaphore::new(limit);
let ex = async_executor::Executor::new();
async_io::block_on(ex.run(async {
let mut tasks = vec![];
// Set up an environment variable for this.
for test in inventory::iter::<__TestStart> {
// Acquire a guard.
let guard = sema.acquire().await;
// Spawn a subprocess.
let mut process = async_process::Command::new(&process_name)
.envs(env::vars_os().chain(Some({
(path(&GUI_TEST_CURRENT_TEST_NAME), path(&test.name))
})))
.spawn()
.expect("failed to spawn child process");
// Spawn a task to poll that subprocess.
let task = ex.spawn(async move {
let _guard = guard;
process.status().await.unwrap()
});
tasks.push(task);
}
// Finish all of the tasks.
for task in tasks {
task.await;
}
}));
}
}
fn path<A: AsRef<OsStr>>(s: &A) -> OsString {
s.as_ref().into()
}

View File

@@ -1,31 +0,0 @@
//! Create a test handler that can be run remotely.
use crate::stream::WriteHandler;
use crate::user::UserHandler;
use crate::TestHandler;
use std::env;
use std::net::TcpStream;
/// Create a test handler adjusted for the current environment.
pub fn handler() -> Box<dyn TestHandler + Send + 'static> {
// If GUI_TEST_UNIX_STREAM is enabled, use that as a Unix stream.
#[cfg(unix)]
if let Some(stream_path) = env::var_os("GUI_TEST_UNIX_STREAM").filter(|s| !s.is_empty()) {
let stream = std::os::unix::net::UnixStream::connect(stream_path)
.expect("unable to connect to gui-test handler");
return Box::new(WriteHandler::new(stream));
}
// If GUI_TEST_TCP_STREAM is enabled, use that as a TCP stream.
if let Some(tcp_ip) = env::var("GUI_TEST_TCP_STREAM")
.ok()
.filter(|s| !s.is_empty())
{
let stream = TcpStream::connect(tcp_ip).unwrap();
return Box::new(WriteHandler::new(stream));
}
// By default, use the user handler.
Box::new(UserHandler::new())
}

View File

@@ -1,39 +0,0 @@
//! Write events to an output stream.
//!
//! The format is as follows:
//! - First 8 bytes: big-endian length of payload.
//! - Next {len} bytes: JSON payload to deserialize from.
use crate::{TestEvent, TestHandler};
use std::io::Write;
/// A wrapper around a writer that sends data down a stream.
#[derive(Debug)]
pub struct WriteHandler<W: Write> {
/// The inner writer.
writer: W,
}
impl<W: Write> WriteHandler<W> {
/// Create a new write handler.
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl<W: Write> TestHandler for WriteHandler<W> {
fn handle_test(&mut self, event: TestEvent) {
let payload = serde_json::to_vec(&event).unwrap();
let length = u64::to_be_bytes(payload.len() as u64);
// Write the payload to the stream.
self.writer.write_all(&length).unwrap();
self.writer.write_all(&payload).unwrap();
}
}
impl<W: Write> Drop for WriteHandler<W> {
fn drop(&mut self) {
self.writer.flush().ok();
}
}

View File

@@ -1,180 +0,0 @@
//! User-facing reporter.
use crate::{TestEvent, TestEventType, TestHandler, TestResult};
use owo_colors::OwoColorize;
use std::collections::BTreeMap;
use std::io::{self, prelude::*};
const TABSIZE: usize = 2;
/// User-facing reporter.
///
/// This reporter dumps events to the console in a user-readable format.
pub struct UserHandler {
/// Current indent.
indent: usize,
/// The test set we're currently displaying.
current_start: Option<String>,
/// Test name we are running, if any.
test_name: Option<String>,
/// Cached events.
cache: BTreeMap<String, Vec<TestEventType>>,
/// Failures we had.
failures: Vec<(String, String)>,
}
impl UserHandler {
/// Create a new handler.
pub fn new() -> Self {
Self {
indent: 0,
current_start: None,
test_name: None,
cache: BTreeMap::new(),
failures: vec![],
}
}
/// Process the provided events.
fn process_events(&mut self, events: impl IntoIterator<Item = TestEvent>) {
for event in events {
// Tell if this is an end event.
let mut ender = matches!(event.ty, TestEventType::Complete { .. });
// If there is no test name set, run the current one.
match self.current_start.as_ref() {
None => {
let TestEvent { runner, ty } = event;
self.current_start = Some(runner);
self.dump_events(Some(ty));
}
Some(test_name) => {
// If there is a test name set and it's ours, post it immediately.
if test_name == &event.runner {
self.dump_events(Some(event.ty));
} else {
// Add it to the back of another one of the events.
self.cache
.entry(test_name.clone())
.or_default()
.push(event.ty);
}
}
}
// If this is the end, dump other events.
while ender {
ender = false;
assert!(self.current_start.take().is_some());
// Pick one set.
if let Some(entry) = self.cache.first_entry() {
let (test_name, entries) = entry.remove_entry();
self.current_start = Some(test_name);
// Dump events and look for a conclusion.
self.dump_events(entries.into_iter().inspect(|ty| {
ender |= matches!(ty, TestEventType::Complete { .. });
}));
}
println!();
}
}
}
/// Dump the provided events to the console.
fn dump_events(&mut self, events: impl IntoIterator<Item = TestEventType>) {
let mut stdout = io::stdout().lock();
for event in events {
// Write the indent.
for _ in 0..(self.indent * TABSIZE) {
stdout.write_all(b" ").unwrap();
}
match event {
TestEventType::GroupStarted { name } => {
assert!(self.test_name.is_none());
// Write the group name and bump the indent.
writeln!(stdout, "{}", name.yellow().italic()).unwrap();
// Add to the indent.
self.indent += 1;
}
TestEventType::GroupEnded => {
assert!(self.test_name.is_none());
// Drop the indent.
self.indent = self.indent.checked_sub(1).unwrap();
}
TestEventType::TestStarted { name } => {
assert!(self.test_name.is_none());
// Write the line.
write!(stdout, "{} ", name.white().italic()).unwrap();
self.test_name = Some(name);
}
TestEventType::TestEnded { result } => {
let test_name = self.test_name.take().unwrap();
// Write the result.
match result {
TestResult::Passed => {
writeln!(stdout, "{}", "ok".green().bold()).unwrap();
}
TestResult::Failed(failure) => {
self.failures.push((test_name, failure));
writeln!(stdout, "{}", "FAIL".red().bold()).unwrap();
}
TestResult::Skipped => {
writeln!(stdout, "{}", "skipped".yellow().bold()).unwrap();
}
}
}
_ => {
// Completion.
}
}
}
}
}
impl TestHandler for UserHandler {
fn handle_test(&mut self, event: TestEvent) {
self.process_events(Some(event));
}
}
impl Drop for UserHandler {
fn drop(&mut self) {
assert!(self.cache.is_empty());
// Write the final bit to the stdout.
let mut stdout = io::stdout().lock();
if !self.failures.is_empty() {
writeln!(stdout, "Test Failures:").ok();
for (test_name, panic) in &self.failures {
writeln!(stdout, " {}", test_name).ok();
writeln!(stdout, "-------------").ok();
writeln!(stdout, "{}", panic).ok();
writeln!(stdout, "-------------").ok();
}
}
}
}

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 format_code_in_doc_comments = true
use_field_init_shorthand=true match_block_trailing_comma = true
# merge_imports=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

@@ -5,7 +5,7 @@ use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId; use crate::window::WindowId;
/// The handler of the application events. /// The handler of the application events.
pub trait ApplicationHandler<T: 'static = ()> { pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed. /// Emitted when new events arrive from the OS to be processed.
/// ///
/// This is a useful place to put code that should be done before you start processing /// This is a useful place to put code that should be done before you start processing
@@ -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 /// 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 /// 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] /// the `Resumed` event is always emitted after the
/// event. /// [`NewEvents(StartCause::Init)`][StartCause::Init] event.
/// ///
/// # Portability /// # Portability
/// ///
@@ -33,15 +33,16 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally /// 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 /// 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 /// 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. /// Also see [`Suspended`] notes.
/// ///
/// ## Android /// ## Android
/// ///
/// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is /// 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 /// expected to closely correlate with the [`onResume`] lifecycle event but there may
/// be a discrepancy. /// technically be a discrepancy.
/// ///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
/// ///
@@ -81,11 +82,95 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// [`Suspended`]: Self::suspended /// [`Suspended`]: Self::suspended
fn resumed(&mut self, event_loop: &ActiveEventLoop); fn resumed(&mut self, event_loop: &ActiveEventLoop);
/// Emitted when an event is sent from [`EventLoopProxy::send_event`]. /// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
/// ///
/// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event /// Multiple calls to the aforementioned method will be merged, and will only wake the event
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) { /// loop once; however, due to the nature of multi-threading some wake ups may appear
let _ = (event_loop, event); /// spuriously. For these reasons, you should not rely on the number of times that this was
/// called.
///
/// The order in which this is emitted in relation to other events is not guaranteed. The time
/// at which this will be emitted is not guaranteed, only that it will happen "soon". That is,
/// there may be several executions of the event loop, including multiple redraws to windows,
/// between [`EventLoopProxy::wake_up()`] being called and the event being delivered.
///
/// [`EventLoopProxy::wake_up()`]: crate::event_loop::EventLoopProxy::wake_up
///
/// # Example
///
/// Use a [`std::sync::mpsc`] channel to handle events from a different thread.
///
/// ```no_run
/// use std::sync::mpsc;
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
/// }
///
/// impl ApplicationHandler for MyApp {
/// # fn window_event(
/// # &mut self,
/// # _event_loop: &ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
/// // Note: We take care not to use `recv` or `iter` here, as those are blocking,
/// // and that would be bad for performance and might lead to a deadlock.
/// for i in self.receiver.try_iter() {
/// println!("received: {i}");
/// }
/// }
///
/// // Rest of `ApplicationHandler`
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = EventLoop::new()?;
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
/// let mut i = 0;
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once `MyApp` is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
/// // Otherwise, `proxy_wake_up` might be triggered prematurely.
/// proxy.wake_up();
/// i += 1;
/// thread::sleep(Duration::from_secs(1));
/// }
/// });
///
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
/// }
/// ```
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
} }
/// Emitted when the OS sends an event to a winit window. /// Emitted when the OS sends an event to a winit window.
@@ -109,7 +194,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when the event loop is about to block and wait for new events. /// 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 /// 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 /// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events. /// ups and also lots of corresponding `AboutToWait` events.
@@ -133,7 +219,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally /// 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 /// 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 /// 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. /// Also see [`Resumed`] notes.
/// ///
@@ -150,7 +237,8 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
/// ///
/// After being `Suspended` on Android applications must drop all render surfaces before /// 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 /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
@@ -186,7 +274,7 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// Emitted when the event loop is being shut down. /// Emitted when the event loop is being shut down.
/// ///
/// This is irreversible - if this method is called, it is guaranteed that the event loop /// 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) { fn exiting(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop; let _ = event_loop;
} }
@@ -197,17 +285,17 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// ///
/// ### Android /// ### Android
/// ///
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application /// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The
/// must [release memory] or risk being killed. /// application must [release memory] or risk being killed.
/// ///
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory() /// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
/// [release memory]: https://developer.android.com/topic/performance/memory#release /// [release memory]: https://developer.android.com/topic/performance/memory#release
/// ///
/// ### iOS /// ### iOS
/// ///
/// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`] /// On iOS, the `MemoryWarning` event is emitted in response to an
/// callback. The application must free as much memory as possible or risk being terminated, see /// [`applicationDidReceiveMemoryWarning`] callback. The application must free as much
/// [how to respond to memory warnings]. /// memory as possible or risk being terminated, see [how to respond to memory warnings].
/// ///
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni /// [`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 /// [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 +307,117 @@ pub trait ApplicationHandler<T: 'static = ()> {
let _ = event_loop; let _ = event_loop;
} }
} }
impl<A: ?Sized + ApplicationHandler> ApplicationHandler 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 proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[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> ApplicationHandler 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 proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[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, //! All notable changes to this project will be documented in this module,
//! along with migration instructions for larger changes. //! along with migration instructions for larger changes.
//!
// Put the current entry at the top of this page, for discoverability. // Put the current entry at the top of this page, for discoverability.
// See `.cargo/config.toml` for details about `unreleased_changelogs`. // See `.cargo/config.toml` for details about `unreleased_changelogs`.
#![cfg_attr(unreleased_changelogs, doc = include_str!("unreleased.md"))] #![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")] #[doc = include_str!("v0.29.md")]
pub mod v0_29 {} pub mod v0_29 {}

View File

@@ -1,42 +1,65 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
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 ## Unreleased
- Deprecate `EventLoop::run` in favor of `EventLoop::run_app`. ### Changed
- Deprecate `EventLoopExtRunOnDemand::run_on_demand` in favor of `EventLoop::run_app_on_demand`.
- Deprecate `EventLoopExtPumpEvents::pump_events` in favor of `EventLoopExtPumpEvents::pump_app_events`. - On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Add `ApplicationHandler<T>` trait which mimics `Event<T>`. - Bump MSRV from `1.70` to `1.73`.
- Move `dpi` types to its own crate, and re-export it from the root crate. - Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
- Implement `Sync` for `EventLoopProxy<T: Send>`. generic user event.
- **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).
- **Breaking:** Rename `EventLoopWindowTarget` to `ActiveEventLoop`. Winit will now only indicate that wake up happened, you will have to pair
- **Breaking:** Remove `Deref` implementation for `EventLoop` that gave `EventLoopWindowTarget`. this with an external mechanism like `std::sync::mpsc::channel` if you want
- **Breaking**: Remove `WindowBuilder` in favor of `WindowAttributes`. to send specific data to be processed on the main thread.
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`. - Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example. only wakes up the loop.
- **Breaking:** Remove `Window::set_cursor_icon`
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor` ### Removed
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs. - Remove `EventLoop::run`.
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s. - Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Add `{Active,}EventLoop::create_custom_cursor` to load custom cursor image sources. - Remove `EventLoopExtPumpEvents::pump_events`.
- On macOS, add services menu. - Remove `Event`.
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation. - On iOS, remove `platform::ios::EventLoopExtIOS` and related `platform::ios::Idiom` type.
- On Web, fix setting cursor icon overriding cursor visibility.
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`. This feature was incomplete, and the equivalent functionality can be trivially achieved outside
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available. of `winit` using `objc2-ui-kit` and calling `UIDevice::currentDevice().userInterfaceIdiom()`.
- **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.

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

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

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

View File

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

View File

@@ -1,7 +1,8 @@
//! The [`Event`] enum and assorted supporting types. //! The event enums and assorted supporting types.
//! //!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get //! 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 //! 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: //! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
@@ -44,22 +45,24 @@ use smol_str::SmolStr;
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time::Instant; use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::ExternalError; use crate::error::ExternalError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)] #[cfg(doc)]
use crate::window::Window; use crate::window::Window;
use crate::{ use crate::window::{ActivationToken, Theme, WindowId};
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial,
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
platform_impl,
window::{ActivationToken, Theme, WindowId},
};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event. /// Describes a generic event.
/// ///
/// See the module-level docs for more information on the event loop manages each event. /// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> { pub(crate) enum Event {
/// See [`ApplicationHandler::new_events`] for details. /// See [`ApplicationHandler::new_events`] for details.
/// ///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
@@ -68,23 +71,14 @@ pub enum Event<T: 'static> {
/// See [`ApplicationHandler::window_event`] for details. /// See [`ApplicationHandler::window_event`] for details.
/// ///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
WindowEvent { #[allow(clippy::enum_variant_names)]
window_id: WindowId, WindowEvent { window_id: WindowId, event: WindowEvent },
event: WindowEvent,
},
/// See [`ApplicationHandler::device_event`] for details. /// See [`ApplicationHandler::device_event`] for details.
/// ///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
DeviceEvent { #[allow(clippy::enum_variant_names)]
device_id: DeviceId, DeviceEvent { device_id: DeviceId, event: DeviceEvent },
event: DeviceEvent,
},
/// See [`ApplicationHandler::user_event`] for details.
///
/// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event
UserEvent(T),
/// See [`ApplicationHandler::suspended`] for details. /// See [`ApplicationHandler::suspended`] for details.
/// ///
@@ -110,24 +104,9 @@ pub enum Event<T: 'static> {
/// ///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
MemoryWarning, MemoryWarning,
}
impl<T> Event<T> { /// User requested a wake up.
#[allow(clippy::result_large_err)] UserWakeUp,
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
} }
/// Describes the reason the event loop is resuming. /// Describes the reason the event loop is resuming.
@@ -138,17 +117,11 @@ pub enum StartCause {
/// guaranteed to be equal to or after the requested resume time. /// guaranteed to be equal to or after the requested resume time.
/// ///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
ResumeTimeReached { ResumeTimeReached { start: Instant, requested_resume: Instant },
start: Instant,
requested_resume: Instant,
},
/// Sent if the OS has new events to send to the window, after a wait was requested. Contains /// 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. /// the moment the wait was requested and the resume time, if requested.
WaitCancelled { WaitCancelled { start: Instant, requested_resume: Option<Instant> },
start: Instant,
requested_resume: Option<Instant>,
},
/// Sent if the event loop is being resumed after the loop's control flow was set to /// Sent if the event loop is being resumed after the loop's control flow was set to
/// [`ControlFlow::Poll`]. /// [`ControlFlow::Poll`].
@@ -164,18 +137,11 @@ pub enum StartCause {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent { pub enum WindowEvent {
/// The activation token was delivered back and now could be used. /// 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`]. /// Delivered in response to [`request_activation_token`].
/// ///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone { ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
serial: AsyncRequestSerial,
token: ActivationToken,
},
/// The size of the window has changed. Contains the client area's new dimensions. /// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>), Resized(PhysicalSize<u32>),
@@ -229,10 +195,10 @@ pub enum WindowEvent {
/// If `true`, the event was generated synthetically by winit /// If `true`, the event was generated synthetically by winit
/// in one of the following circumstances: /// in one of the following circumstances:
/// ///
/// * Synthetic key press events are generated for all keys pressed /// * Synthetic key press events are generated for all keys pressed when a window gains
/// when a window gains focus. Likewise, synthetic key release events /// focus. Likewise, synthetic key release events are generated for all keys pressed when
/// are generated for all keys pressed when a window goes out of focus. /// a window goes out of focus. ***Currently, this is only functional on X11 and
/// ***Currently, this is only functional on X11 and Windows*** /// Windows***
/// ///
/// Otherwise, this value is always `false`. /// Otherwise, this value is always `false`.
is_synthetic: bool, is_synthetic: bool,
@@ -262,9 +228,10 @@ pub enum WindowEvent {
CursorMoved { CursorMoved {
device_id: DeviceId, device_id: DeviceId,
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// of this data is limited by the display area and it may have been transformed by
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. /// 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>, position: PhysicalPosition<f64>,
}, },
@@ -291,18 +258,10 @@ pub enum WindowEvent {
CursorLeft { device_id: DeviceId }, CursorLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred. /// A mouse wheel movement or touchpad scroll occurred.
MouseWheel { MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase },
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
},
/// An mouse button press has been received. /// An mouse button press has been received.
MouseInput { MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton },
device_id: DeviceId,
state: ElementState,
button: MouseButton,
},
/// Two-finger pinch gesture, often used for magnification. /// Two-finger pinch gesture, often used for magnification.
/// ///
@@ -320,6 +279,19 @@ pub enum WindowEvent {
phase: TouchPhase, 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. /// Double tap gesture.
/// ///
/// On a Mac, smart magnification is triggered by a double tap with two fingers /// On a Mac, smart magnification is triggered by a double tap with two fingers
@@ -351,6 +323,7 @@ pub enum WindowEvent {
/// - On iOS, not recognized by default. It must be enabled when needed. /// - On iOS, not recognized by default. It must be enabled when needed.
RotationGesture { RotationGesture {
device_id: DeviceId, device_id: DeviceId,
/// change in rotation in degrees
delta: f32, delta: f32,
phase: TouchPhase, phase: TouchPhase,
}, },
@@ -358,20 +331,12 @@ pub enum WindowEvent {
/// Touchpad pressure event. /// Touchpad pressure event.
/// ///
/// At the moment, only supported on Apple forcetouch-capable macbooks. /// 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 /// The parameters are: pressure level (value between 0 and 1 representing how hard the
/// is being pressed) and stage (integer representing the click level). /// touchpad is being pressed) and stage (integer representing the click level).
TouchpadPressure { TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
device_id: DeviceId,
pressure: f32,
stage: i64,
},
/// Motion on some analog axis. May report data redundant to other, more specific events. /// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion { AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
device_id: DeviceId,
axis: AxisId,
value: f64,
},
/// Touch event has been received /// Touch event has been received
/// ///
@@ -393,8 +358,8 @@ pub enum WindowEvent {
/// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Changing the display's scale factor (e.g. in Control Panel on Windows).
/// * Moving the window to a display with a different scale factor. /// * 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 /// To update the window size, use the provided [`InnerSizeWriter`] handle. By default, the
/// resized to the value suggested by the OS, but it can be changed to any value. /// 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. /// For more information about DPI in general, see the [`dpi`] crate.
ScaleFactorChanged { ScaleFactorChanged {
@@ -410,6 +375,8 @@ pub enum WindowEvent {
/// Applications might wish to react to this to change the theme of the content of the window /// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme. /// 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 /// ## Platform-specific
/// ///
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported. /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
@@ -424,10 +391,11 @@ pub enum WindowEvent {
/// ///
/// ### iOS /// ### iOS
/// ///
/// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`] /// On iOS, the `Occluded(false)` event is emitted in response to an
/// callback which means the application should start preparing its data. The `Occluded(true)` event is /// [`applicationWillEnterForeground`] callback which means the application should start
/// emitted in response to an [`applicationDidEnterBackground`] callback which means the application /// preparing its data. The `Occluded(true)` event is emitted in response to an
/// should free resources (according to the [iOS application lifecycle]). /// [`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 /// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground
/// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground /// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground
@@ -457,9 +425,10 @@ pub enum WindowEvent {
/// Identifier of an input device. /// Identifier of an input device.
/// ///
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which /// Whenever you receive an event arising from a particular input device, this event contains a
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or /// `DeviceId` which identifies its origin. Note that devices may be virtual (representing an
/// physical. Virtual devices typically aggregate inputs from multiple physical devices. /// 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId); pub struct DeviceId(pub(crate) platform_impl::DeviceId);
@@ -481,10 +450,10 @@ impl DeviceId {
/// Represents raw hardware events that are not associated with any particular window. /// 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 /// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because /// or first-person game controls. Many physical actions, such as mouse movement, can produce both
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs /// device and window events. Because window events typically arise from virtual devices
/// may not match. /// (corresponding to GUI cursors and keyboard focus) the device IDs may not match.
/// ///
/// Note that these events are delivered regardless of input focus. /// Note that these events are delivered regardless of input focus.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@@ -494,7 +463,8 @@ pub enum DeviceEvent {
/// Change in physical position of a pointing device. /// 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 { MouseMotion {
/// (x, y) change in position in unspecified units. /// (x, y) change in position in unspecified units.
/// ///
@@ -615,13 +585,13 @@ pub struct KeyEvent {
/// Contains the location of this key on the keyboard. /// 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 /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift"
/// appears on the left side of the QWERTY keyboard as well as the right side. However, both keys /// key appears on the left side of the QWERTY keyboard as well as the right side. However,
/// have the same symbolic value. Another example of this phenomenon is the "1" key, which appears /// both keys have the same symbolic value. Another example of this phenomenon is the "1"
/// both above the "Q" key and as the "Keypad 1" key. /// 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 /// This field allows the user to differentiate between keys like this that have the same
/// value but different locations on the keyboard. /// symbolic value but different locations on the keyboard.
/// ///
/// See the [`KeyLocation`] type for more details. /// See the [`KeyLocation`] type for more details.
/// ///
@@ -636,8 +606,8 @@ pub struct KeyEvent {
/// Whether or not this key is a key repeat event. /// 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 /// 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 /// as though it were being pressed and released repeatedly. This field is `true` if and only
/// this event is the result of one of those repeats. /// if this event is the result of one of those repeats.
/// ///
/// # Example /// # Example
/// ///
@@ -645,30 +615,31 @@ pub struct KeyEvent {
/// done by ignoring events where this property is set. /// 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}; /// use winit::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile /// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event { /// match window_event {
/// WindowEvent::KeyboardInput { /// WindowEvent::KeyboardInput {
/// event: KeyEvent { /// event:
/// physical_key: PhysicalKey::Code(KeyCode::KeyW), /// KeyEvent {
/// state: ElementState::Pressed, /// physical_key: PhysicalKey::Code(KeyCode::KeyW),
/// repeat: false, /// state: ElementState::Pressed,
/// .. /// repeat: false,
/// }, /// ..
/// },
/// .. /// ..
/// } => { /// } => {
/// // The physical key `W` was pressed, and it was not a repeat /// // The physical key `W` was pressed, and it was not a repeat
/// } /// },
/// _ => {} // Handle other events /// _ => {}, // Handle other events
/// } /// }
/// ``` /// ```
pub repeat: bool, pub repeat: bool,
/// Platform-specific key event information. /// Platform-specific key event information.
/// ///
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with all /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// modifiers applied. /// all modifiers applied.
/// ///
/// On Android, iOS, Redox and Web, this type is a no-op. /// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra, pub(crate) platform_specific: platform_impl::KeyEventExtra,
@@ -742,10 +713,7 @@ impl Modifiers {
impl From<ModifiersState> for Modifiers { impl From<ModifiersState> for Modifiers {
fn from(value: ModifiersState) -> Self { fn from(value: ModifiersState) -> Self {
Self { Self { state: value, pressed_mods: Default::default() }
state: value,
pressed_mods: Default::default(),
}
} }
} }
@@ -753,12 +721,16 @@ impl From<ModifiersState> for Modifiers {
/// ///
/// This is also called a "composition event". /// This is also called a "composition event".
/// ///
/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::KeyboardInput`]. /// Most keypresses using a latin-like keyboard layout simply generate a
/// However, one couldn't possibly have a key for every single unicode character that the user might want to type /// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
/// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead. /// 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 /// ```ignore
/// // Press "`" key /// // Press "`" key
/// Ime::Preedit("`", Some((0, 0))) /// Ime::Preedit("`", Some((0, 0)))
@@ -767,11 +739,13 @@ impl From<ModifiersState> for Modifiers {
/// Ime::Commit("é") /// Ime::Commit("é")
/// ``` /// ```
/// ///
/// Additionally, certain input devices are configured to display a candidate box that allow the user to select the /// Additionally, certain input devices are configured to display a candidate box that allow the
/// desired character interactively. (To properly position this box, you must use [`Window::set_ime_cursor_area`].) /// 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 /// ```ignore
/// // Press "A" key /// // Press "A" key
/// Ime::Preedit("a", Some((1, 1))) /// Ime::Preedit("a", Some((1, 1)))
@@ -813,8 +787,8 @@ pub enum Ime {
/// ///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or /// 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 /// [`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 /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear
/// preedit text. /// pending preedit text.
Disabled, Disabled,
} }
@@ -913,17 +887,13 @@ impl Force {
/// consistent across devices. /// consistent across devices.
pub fn normalized(&self) -> f64 { pub fn normalized(&self) -> f64 {
match self { match self {
Force::Calibrated { Force::Calibrated { force, max_possible_force, altitude_angle } => {
force,
max_possible_force,
altitude_angle,
} => {
let force = match altitude_angle { let force = match altitude_angle {
Some(altitude_angle) => force / altitude_angle.sin(), Some(altitude_angle) => force / altitude_angle.sin(),
None => *force, None => *force,
}; };
force / max_possible_force force / max_possible_force
} },
Force::Normalized(force) => *force, Force::Normalized(force) => *force,
} }
} }
@@ -1029,6 +999,7 @@ impl PartialEq for InnerSizeWriter {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::dpi::PhysicalPosition;
use crate::event; use crate::event;
use std::collections::{BTreeSet, HashSet}; use std::collections::{BTreeSet, HashSet};
@@ -1040,12 +1011,13 @@ mod tests {
#[allow(deprecated)] #[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; use crate::window::WindowId;
// Mainline events. // Mainline events.
let wid = unsafe { WindowId::dummy() }; let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init)); x(NewEvents(event::StartCause::Init));
x(AboutToWait); x(AboutToWait);
x(LoopExiting); x(LoopExiting);
@@ -1053,12 +1025,7 @@ mod tests {
x(Resumed); x(Resumed);
// Window events. // Window events.
let with_window_event = |wev| { let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
x(WindowEvent {
window_id: wid,
event: wev,
})
};
with_window_event(CloseRequested); with_window_event(CloseRequested);
with_window_event(Destroyed); with_window_event(Destroyed);
@@ -1069,10 +1036,7 @@ mod tests {
with_window_event(HoveredFile("x.txt".into())); with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled); with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled)); with_window_event(Ime(Enabled));
with_window_event(CursorMoved { with_window_event(CursorMoved { device_id: did, position: (0, 0).into() });
device_id: did,
position: (0, 0).into(),
});
with_window_event(ModifiersChanged(event::Modifiers::default())); with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did }); with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did }); with_window_event(CursorLeft { device_id: did });
@@ -1097,16 +1061,13 @@ mod tests {
delta: 0.0, delta: 0.0,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,
}); });
with_window_event(TouchpadPressure { with_window_event(PanGesture {
device_id: did, device_id: did,
pressure: 0.0, delta: PhysicalPosition::<f32>::new(0.0, 0.0),
stage: 0, phase: event::TouchPhase::Started,
});
with_window_event(AxisMotion {
device_id: did,
axis: 0,
value: 0.0,
}); });
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 { with_window_event(Touch(event::Touch {
device_id: did, device_id: did,
phase: event::TouchPhase::Started, phase: event::TouchPhase::Started,
@@ -1122,29 +1083,17 @@ mod tests {
{ {
use event::DeviceEvent::*; use event::DeviceEvent::*;
let with_device_event = |dev_ev| { let with_device_event =
x(event::Event::DeviceEvent { |dev_ev| x(event::Event::DeviceEvent { device_id: did, event: dev_ev });
device_id: did,
event: dev_ev,
})
};
with_device_event(Added); with_device_event(Added);
with_device_event(Removed); with_device_event(Removed);
with_device_event(MouseMotion { with_device_event(MouseMotion { delta: (0.0, 0.0).into() });
delta: (0.0, 0.0).into(),
});
with_device_event(MouseWheel { with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
}); });
with_device_event(Motion { with_device_event(Motion { axis: 0, value: 0.0 });
axis: 0, with_device_event(Button { button: 0, state: event::ElementState::Pressed });
value: 0.0,
});
with_device_event(Button {
button: 0,
state: event::ElementState::Pressed,
});
} }
}}; }};
} }
@@ -1152,35 +1101,19 @@ mod tests {
#[allow(clippy::redundant_clone)] #[allow(clippy::redundant_clone)]
#[test] #[test]
fn test_event_clone() { fn test_event_clone() {
foreach_event!(|event: event::Event<()>| { foreach_event!(|event: event::Event| {
let event2 = event.clone(); let event2 = event.clone();
assert_eq!(event, event2); assert_eq!(event, event2);
}) })
} }
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test] #[test]
fn test_force_normalize() { fn test_force_normalize() {
let force = event::Force::Normalized(0.0); let force = event::Force::Normalized(0.0);
assert_eq!(force.normalized(), 0.0); assert_eq!(force.normalized(), 0.0);
let force2 = event::Force::Calibrated { let force2 =
force: 5.0, event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: None };
max_possible_force: 2.5,
altitude_angle: None,
};
assert_eq!(force2.normalized(), 2.0); assert_eq!(force2.normalized(), 2.0);
let force3 = event::Force::Calibrated { let force3 = event::Force::Calibrated {
@@ -1194,7 +1127,7 @@ mod tests {
#[allow(clippy::clone_on_copy)] #[allow(clippy::clone_on_copy)]
#[test] #[test]
fn ensure_attrs_do_not_panic() { fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| { foreach_event!(|event: event::Event| {
let _ = format!("{:?}", event); let _ = format!("{:?}", event);
}); });
let _ = event::StartCause::Init.clone(); let _ = event::StartCause::Init.clone();
@@ -1219,11 +1152,8 @@ mod tests {
force: Some(event::Force::Normalized(0.0)), force: Some(event::Force::Normalized(0.0)),
} }
.clone(); .clone();
let _ = event::Force::Calibrated { let _ =
force: 0.0, event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None }
max_possible_force: 0.0, .clone();
altitude_angle: None,
}
.clone();
} }
} }

View File

@@ -3,15 +3,16 @@
//! //!
//! If you want to send custom events to the event loop, use //! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its //! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`send_event`][EventLoopProxy::send_event] method. //! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//! //!
//! See the root-level documentation for information on how to create and use an event loop to //! See the root-level documentation for information on how to create and use an event loop to
//! handle events. //! handle events.
use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt};
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -20,8 +21,9 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError}; use crate::error::{EventLoopError, OsError};
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// Provides a way to retrieve events from the system and from the windows that were registered to /// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop. /// the events loop.
@@ -33,13 +35,13 @@ use crate::{event::Event, monitor::MonitorHandle, platform_impl};
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs. /// 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 /// 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 /// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// [`Window`] created from this _can_ be sent to an other thread, and the /// 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. /// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
pub struct EventLoop<T: 'static> { pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop<T>, pub(crate) event_loop: platform_impl::EventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
} }
@@ -57,16 +59,15 @@ pub struct ActiveEventLoop {
/// This is used to make specifying options that affect the whole application /// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported. /// easier. But note that constructing multiple event loops is not supported.
/// ///
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`]. /// This can be created using [`EventLoop::builder`].
#[derive(Default)] #[derive(Default)]
pub struct EventLoopBuilder<T: 'static> { pub struct EventLoopBuilder {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
} }
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder<()> { impl EventLoopBuilder {
/// Start building a new event loop. /// Start building a new event loop.
#[inline] #[inline]
#[deprecated = "use `EventLoop::builder` instead"] #[deprecated = "use `EventLoop::builder` instead"]
@@ -75,7 +76,7 @@ impl EventLoopBuilder<()> {
} }
} }
impl<T> EventLoopBuilder<T> { impl EventLoopBuilder {
/// Builds a new event loop. /// Builds a new event loop.
/// ///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -94,22 +95,23 @@ impl<T> EventLoopBuilder<T> {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or
/// or `DISPLAY` respectively when building the event loop. /// `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling /// - **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 /// [`platform`]: crate::platform
#[cfg_attr( #[cfg_attr(
android, android_platform,
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app" doc = "[`.with_android_app(app)`]: \
crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)] )]
#[cfg_attr( #[cfg_attr(
not(android), not(android_platform),
doc = "[`.with_android_app(app)`]: #only-available-on-android" doc = "[`.with_android_app(app)`]: #only-available-on-android"
)] )]
#[inline] #[inline]
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> { pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered(); let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
@@ -130,7 +132,7 @@ impl<T> EventLoopBuilder<T> {
} }
} }
impl<T> fmt::Debug for EventLoop<T> { impl fmt::Debug for EventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoop { .. }") f.pad("EventLoop { .. }")
} }
@@ -144,11 +146,12 @@ impl fmt::Debug for ActiveEventLoop {
/// Set through [`ActiveEventLoop::set_control_flow()`]. /// Set through [`ActiveEventLoop::set_control_flow()`].
/// ///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted. /// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
/// ///
/// Defaults to [`Wait`]. /// Defaults to [`Wait`].
/// ///
/// [`Wait`]: Self::Wait /// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow { pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of /// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -162,9 +165,9 @@ pub enum ControlFlow {
/// When the current loop iteration finishes, suspend the thread until either another event /// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached. /// arrives or the given time is reached.
/// ///
/// Useful for implementing efficient timers. Applications which want to render at the display's /// Useful for implementing efficient timers. Applications which want to render at the
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API /// display's native refresh rate should instead use [`Poll`] and the VSync functionality
/// to reduce odds of missed frames. /// of a graphics API to reduce odds of missed frames.
/// ///
/// [`Poll`]: Self::Poll /// [`Poll`]: Self::Poll
WaitUntil(Instant), WaitUntil(Instant),
@@ -186,12 +189,12 @@ impl ControlFlow {
} }
} }
impl EventLoop<()> { impl EventLoop {
/// Create the event loop. /// Create the event loop.
/// ///
/// This is an alias of `EventLoop::builder().build()`. /// This is an alias of `EventLoop::builder().build()`.
#[inline] #[inline]
pub fn new() -> Result<EventLoop<()>, EventLoopError> { pub fn new() -> Result<EventLoop, EventLoopError> {
Self::builder().build() Self::builder().build()
} }
@@ -201,36 +204,12 @@ impl EventLoop<()> {
/// ///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that. /// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline] #[inline]
pub fn builder() -> EventLoopBuilder<()> { pub fn builder() -> EventLoopBuilder {
Self::with_user_event() EventLoopBuilder { platform_specific: Default::default() }
} }
} }
impl<T> EventLoop<T> { impl EventLoop {
/// 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,
}
}
/// See [`run_app`].
///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -239,9 +218,9 @@ impl<T> EventLoop<T> {
/// ///
/// - **iOS:** Will never return to the caller and so values not passed to this function will /// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits. /// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
/// (that Rust doesn't see) that will also mean that the rest of the function is never executed /// exception (that Rust doesn't see) that will also mean that the rest of the function is
/// and any values not passed to this function will *not* be dropped. /// never executed and any values not passed to this function will *not* be dropped.
/// ///
/// Web applications are recommended to use /// Web applications are recommended to use
#[cfg_attr( #[cfg_attr(
@@ -261,26 +240,21 @@ impl<T> EventLoop<T> {
/// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web. /// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
#[inline] #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))] #[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> { pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop self.event_loop.run_app(app)
.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread. /// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() }
event_loop_proxy: self.event_loop.create_proxy(),
}
} }
/// Gets a persistent reference to the underlying platform display. /// Gets a persistent reference to the underlying platform display.
/// ///
/// See the [`OwnedDisplayHandle`] type for more information. /// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle { pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle { OwnedDisplayHandle { platform: self.event_loop.window_target().p.owned_display_handle() }
platform: self.event_loop.window_target().p.owned_display_handle(),
}
} }
/// Change if or when [`DeviceEvent`]s are captured. /// Change if or when [`DeviceEvent`]s are captured.
@@ -295,18 +269,12 @@ impl<T> EventLoop<T> {
) )
.entered(); .entered();
self.event_loop self.event_loop.window_target().p.listen_device_events(allowed);
.window_target()
.p
.listen_device_events(allowed);
} }
/// Sets the [`ControlFlow`]. /// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) { pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop self.event_loop.window_target().p.set_control_flow(control_flow)
.window_target()
.p
.set_control_flow(control_flow)
} }
/// Create a window. /// Create a window.
@@ -329,22 +297,19 @@ impl<T> EventLoop<T> {
/// Create custom cursor. /// Create custom cursor.
pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor { pub fn create_custom_cursor(&self, custom_cursor: CustomCursorSource) -> CustomCursor {
self.event_loop self.event_loop.window_target().p.create_custom_cursor(custom_cursor)
.window_target()
.p
.create_custom_cursor(custom_cursor)
} }
} }
#[cfg(feature = "rwh_06")] #[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> { impl rwh_06::HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> { fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target()) rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
} }
} }
#[cfg(feature = "rwh_05")] #[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> { unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target()) rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
@@ -352,7 +317,7 @@ unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
} }
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
impl<T> AsFd for EventLoop<T> { impl AsFd for EventLoop {
/// Get the underlying [EventLoop]'s `fd` which you can register /// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API. /// loop must be polled with the [`pump_app_events`] API.
@@ -366,7 +331,7 @@ impl<T> AsFd for EventLoop<T> {
} }
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
impl<T> AsRawFd for EventLoop<T> { impl AsRawFd for EventLoop {
/// Get the underlying [EventLoop]'s raw `fd` which you can register /// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API. /// loop must be polled with the [`pump_app_events`] API.
@@ -413,10 +378,7 @@ impl ActiveEventLoop {
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered(); let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
#[allow(clippy::useless_conversion)] // false positive on some platforms #[allow(clippy::useless_conversion)] // false positive on some platforms
self.p self.p.available_monitors().into_iter().map(|inner| MonitorHandle { inner })
.available_monitors()
.into_iter()
.map(|inner| MonitorHandle { inner })
} }
/// Returns the primary monitor of the system. /// Returns the primary monitor of the system.
@@ -430,9 +392,7 @@ impl ActiveEventLoop {
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered(); let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
self.p self.p.primary_monitor().map(|inner| MonitorHandle { inner })
.primary_monitor()
.map(|inner| MonitorHandle { inner })
} }
/// Change if or when [`DeviceEvent`]s are captured. /// Change if or when [`DeviceEvent`]s are captured.
@@ -468,7 +428,7 @@ impl ActiveEventLoop {
/// This exits the event loop. /// This exits the event loop.
/// ///
/// See [`LoopExiting`][Event::LoopExiting]. /// See [`exiting`][crate::application::ApplicationHandler::exiting].
pub fn exit(&self) { pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered(); let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -486,9 +446,7 @@ impl ActiveEventLoop {
/// ///
/// See the [`OwnedDisplayHandle`] type for more information. /// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle { pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle { OwnedDisplayHandle { platform: self.p.owned_display_handle() }
platform: self.p.owned_display_handle(),
}
} }
} }
@@ -555,55 +513,39 @@ unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
} }
} }
/// Used to send custom events to [`EventLoop`]. /// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
pub struct EventLoopProxy<T: 'static> { #[derive(Clone)]
event_loop_proxy: platform_impl::EventLoopProxy<T>, pub struct EventLoopProxy {
event_loop_proxy: platform_impl::EventLoopProxy,
} }
impl<T: 'static> Clone for EventLoopProxy<T> { impl EventLoopProxy {
fn clone(&self) -> Self { /// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
Self { /// called.
event_loop_proxy: self.event_loop_proxy.clone(), ///
} /// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.event_loop_proxy.wake_up();
} }
} }
impl<T: 'static> EventLoopProxy<T> { impl fmt::Debug for EventLoopProxy {
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
self.event_loop_proxy.send_event(event)
}
}
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopProxy { .. }") f.pad("EventLoopProxy { .. }")
} }
} }
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Control when device events are captured. /// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents { pub enum DeviceEvents {
@@ -641,23 +583,3 @@ impl AsyncRequestSerial {
Self { serial } Self { serial }
} }
} }
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserEvent(event) => app.user_event(event_loop, event),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
}
}

View File

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

View File

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

View File

@@ -19,33 +19,21 @@
//! window or a key getting pressed while the window is focused. Devices can generate //! 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. //! [`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 //! 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 retrieve events by calling [`EventLoop::run_app()`]. This function will //! 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 //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//! //!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop //! 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 //! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works
//! most other platforms. However, this model can be re-implemented to an extent with //! poorly on most other platforms. However, this model can be re-implemented to an extent with
#![cfg_attr( #![cfg_attr(
any( any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform),
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
),
doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]" doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]"
)] )]
#![cfg_attr( #![cfg_attr(
not(any( not(any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform)),
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform
)),
doc = "`EventLoopExtPumpEvents::pump_app_events()`" doc = "`EventLoopExtPumpEvents::pump_app_events()`"
)] )]
//! [^1]. See that method's documentation for more reasons about why //! [^1]. See that method's documentation for more reasons about why
@@ -116,16 +104,16 @@
//! //!
//! # Drawing on the window //! # Drawing on the window
//! //!
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you to //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However, it allows you
//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the //! 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 //! [`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. //! 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 //! 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 //! 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 //! 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 //! [`visible` set to `false`][crate::window::WindowAttributes::with_visible] and explicitly make
//! window visible only once you're ready to render into it. //! the window visible only once you're ready to render into it.
//! //!
//! # UI scaling //! # UI scaling
//! //!
@@ -151,13 +139,11 @@
//! Winit provides the following Cargo features: //! Winit provides the following Cargo features:
//! //!
//! * `x11` (enabled by default): On Unix platforms, enables the X11 backend. //! * `x11` (enabled by default): On Unix platforms, enables the X11 backend.
//! * `wayland` (enabled by default): On Unix platforms, enables the Wayland //! * `wayland` (enabled by default): On Unix platforms, enables the Wayland backend.
//! backend.
//! * `rwh_04`: Implement `raw-window-handle v0.4` traits. //! * `rwh_04`: Implement `raw-window-handle v0.4` traits.
//! * `rwh_05`: Implement `raw-window-handle v0.5` traits. //! * `rwh_05`: Implement `raw-window-handle v0.5` traits.
//! * `rwh_06`: Implement `raw-window-handle v0.6` traits. //! * `rwh_06`: Implement `raw-window-handle v0.6` traits.
//! * `serde`: Enables serialization/deserialization of certain types with //! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions. //! * `mint`: Enables mint (math interoperability standard types) conversions.
//! //!
//! See the [`platform`] module for documentation on platform-specific cargo //! See the [`platform`] module for documentation on platform-specific cargo
@@ -176,7 +162,7 @@
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent //! [`Event::UserEvent`]: event::Event::UserEvent
//! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. //! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
@@ -186,14 +172,15 @@
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(clippy, deny(warnings))] #![cfg_attr(clippy, deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly
#![cfg_attr( // doc
docsrs, #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
#![allow(clippy::missing_safety_doc)] #![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")] #[cfg(feature = "rwh_06")]
pub use rwh_06 as raw_window_handle; pub use rwh_06 as raw_window_handle;

View File

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

View File

@@ -19,6 +19,7 @@
//! //!
//! | winit | ndk-glue | //! | winit | ndk-glue |
//! | :---: | :--------------------------: | //! | :---: | :--------------------------: |
//! | 0.30 | `android-activity = "0.6"` |
//! | 0.29 | `android-activity = "0.5"` | //! | 0.29 | `android-activity = "0.5"` |
//! | 0.28 | `android-activity = "0.4"` | //! | 0.28 | `android-activity = "0.4"` |
//! | 0.27 | `ndk-glue = "0.7"` | //! | 0.27 | `ndk-glue = "0.7"` |
@@ -58,23 +59,26 @@
//! //!
//! ## Converting from `ndk-glue` to `android-activity` //! ## 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` //! 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" ] }` //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3",
//! 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). //! features = [ "android-native-activity" ] }`
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above). //! 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::{ use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}, use crate::window::{Window, WindowAttributes};
window::{Window, WindowAttributes},
};
use self::activity::{AndroidApp, ConfigurationRef, Rect}; use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {} pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {} impl EventLoopExtAndroid for EventLoop {}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android. /// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {} pub trait ActiveEventLoopExtAndroid {}
@@ -115,7 +119,7 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self; fn handle_volume_keys(&mut self) -> &mut Self;
} }
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> { impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self { fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app); self.platform_specific.android_app = Some(app);
self self
@@ -146,7 +150,7 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// 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: /// their `android_main(app: AndroidApp)` function like:
/// ```rust /// ```rust
/// #[cfg(target_os="android")] /// #[cfg(target_os = "android")]
/// use winit::platform::android::activity::AndroidApp; /// use winit::platform::android::activity::AndroidApp;
/// ``` /// ```
pub mod activity { pub mod activity {

View File

@@ -66,23 +66,8 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use crate::{ use crate::monitor::{MonitorHandle, VideoModeHandle};
event_loop::EventLoop, use crate::window::{Window, WindowAttributes};
monitor::{MonitorHandle, VideoModeHandle},
window::{Window, WindowAttributes},
};
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn idiom(&self) -> Idiom {
self.event_loop.idiom()
}
}
/// Additional methods on [`Window`] that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
@@ -157,6 +142,21 @@ pub trait WindowExtIOS {
/// The default is to not recognize gestures. /// The default is to not recognize gestures.
fn recognize_pinch_gesture(&self, should_recognize: bool); 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. /// Sets whether the [`Window`] should recognize double tap gestures.
/// ///
/// The default is to not recognize gestures. /// The default is to not recognize gestures.
@@ -171,20 +171,17 @@ pub trait WindowExtIOS {
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
#[inline] #[inline]
fn set_scale_factor(&self, scale_factor: f64) { fn set_scale_factor(&self, scale_factor: f64) {
self.window self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
} }
#[inline] #[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
} }
#[inline] #[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) { fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
} }
#[inline] #[inline]
@@ -196,32 +193,43 @@ impl WindowExtIOS for Window {
#[inline] #[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) { fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
} }
#[inline] #[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
self.window self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
} }
#[inline] #[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) { fn recognize_pinch_gesture(&self, should_recognize: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
.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] #[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) { fn recognize_doubletap_gesture(&self, should_recognize: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
} }
#[inline] #[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) { fn recognize_rotation_gesture(&self, should_recognize: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
} }
} }
@@ -301,8 +309,7 @@ impl WindowAttributesExtIOS for WindowAttributes {
#[inline] #[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
.preferred_screen_edges_deferring_system_gestures = edges;
self self
} }
@@ -336,15 +343,13 @@ impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoModeHandle { fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle { VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
video_mode: self.inner.preferred_video_mode(),
}
} }
} }
@@ -361,24 +366,6 @@ pub enum ValidOrientations {
Portrait, Portrait,
} }
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}
bitflags::bitflags! { bitflags::bitflags! {
/// The [edges] of a screen. /// The [edges] of a screen.
/// ///

View File

@@ -19,11 +19,9 @@ use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
event_loop::{ActiveEventLoop, EventLoopBuilder}, use crate::monitor::MonitorHandle;
monitor::MonitorHandle, use crate::window::{Window, WindowAttributes};
window::{Window, WindowAttributes},
};
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS { pub trait WindowExtMacOS {
@@ -106,8 +104,7 @@ impl WindowExtMacOS for Window {
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
self.window self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
@@ -117,14 +114,12 @@ impl WindowExtMacOS for Window {
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { fn set_has_shadow(&self, has_shadow: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
} }
#[inline] #[inline]
fn set_tabbing_identifier(&self, identifier: &str) { fn set_tabbing_identifier(&self, identifier: &str) {
self.window self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
} }
#[inline] #[inline]
@@ -144,8 +139,7 @@ impl WindowExtMacOS for Window {
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { fn select_tab_at_index(&self, index: usize) {
self.window self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
} }
#[inline] #[inline]
@@ -160,14 +154,12 @@ impl WindowExtMacOS for Window {
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { fn set_document_edited(&self, edited: bool) {
self.window self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
.maybe_queue_on_main(move |w| w.set_document_edited(edited))
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
self.window self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
} }
#[inline] #[inline]
@@ -192,7 +184,8 @@ pub enum ActivationPolicy {
/// Additional methods on [`WindowAttributes`] that are specific to MacOS. /// 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_titlebar_transparent`
/// - `with_title_hidden` /// - `with_title_hidden`
/// - `with_titlebar_hidden` /// - `with_titlebar_hidden`
@@ -282,9 +275,7 @@ impl WindowAttributesExtMacOS for WindowAttributes {
#[inline] #[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
.tabbing_identifier
.replace(tabbing_identifier.to_string());
self self
} }
@@ -307,7 +298,7 @@ pub trait EventLoopBuilderExtMacOS {
/// ``` /// ```
/// use winit::event_loop::EventLoopBuilder; /// use winit::event_loop::EventLoopBuilder;
/// #[cfg(target_os = "macos")] /// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy}; /// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
/// ///
/// let mut builder = EventLoopBuilder::new(); /// let mut builder = EventLoopBuilder::new();
/// #[cfg(target_os = "macos")] /// #[cfg(target_os = "macos")]
@@ -347,7 +338,7 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self; fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
} }
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> { impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[inline] #[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self { fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = activation_policy; self.platform_specific.activation_policy = activation_policy;
@@ -383,18 +374,18 @@ impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> { fn ns_screen(&self) -> Option<*mut c_void> {
// SAFETY: We only use the marker to get a pointer // SAFETY: We only use the marker to get a pointer
let mtm = unsafe { icrate::Foundation::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
self.inner self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
.ns_screen(mtm)
.map(|s| objc2::rc::Id::as_ptr(&s) as _)
} }
} }
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS. /// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
pub trait ActiveEventLoopExtMacOS { 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); 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); fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs. /// Set whether the system can automatically organize windows into tabs.
/// ///

View File

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

View File

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

View File

@@ -1,14 +1,10 @@
use std::time::Duration; use std::time::Duration;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::event::Event; use crate::event_loop::EventLoop;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
/// Additional methods on [`EventLoop`] for pumping events within an external event loop /// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents { pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events. /// Pump the `EventLoop` to check for and dispatch pending events.
/// ///
/// This API is designed to enable applications to integrate Winit into an /// This API is designed to enable applications to integrate Winit into an
@@ -84,12 +80,11 @@ pub trait EventLoopExtPumpEvents {
/// ///
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Windows**: The implementation will use `PeekMessage` when checking for /// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
/// window messages to avoid blocking your external event loop. /// to avoid blocking your external event loop.
/// ///
/// - **MacOS**: The implementation works in terms of stopping the global application /// - **MacOS**: The implementation works in terms of stopping the global application whenever
/// whenever the application `RunLoop` indicates that it is preparing to block /// the application `RunLoop` indicates that it is preparing to block and wait for new events.
/// and wait for new events.
/// ///
/// This is very different to the polling APIs that are available on other /// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private /// platforms (the lower level polling primitives on MacOS are private
@@ -104,34 +99,20 @@ pub trait EventLoopExtPumpEvents {
/// If you render outside of Winit you are likely to see window resizing artifacts /// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect` /// since MacOS expects applications to render synchronously during any `drawRect`
/// callback. /// callback.
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>( fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
&mut self, &mut self,
timeout: Option<Duration>, timeout: Option<Duration>,
app: &mut A, app: &mut A,
) -> PumpStatus { ) -> PumpStatus {
#[allow(deprecated)] self.event_loop.pump_app_events(timeout, app)
self.pump_events(timeout, |event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
/// See [`pump_app_events`].
///
/// [`pump_app_events`]: Self::pump_app_events
#[deprecated = "use EventLoopExtPumpEvents::pump_app_events"]
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.pump_events(timeout, event_handler)
} }
} }

View File

@@ -1,28 +1,16 @@
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::EventLoopError; use crate::error::EventLoopError;
use crate::event::Event; use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(doc)] #[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller. /// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand { pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent: 'static;
/// See [`run_app_on_demand`].
///
/// [`run_app_on_demand`]: Self::run_app_on_demand
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Run the application with the event loop on the calling thread. /// 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 /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// and it is possible to return control back to the caller without /// closures and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and /// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit. /// so the event loop can be re-run after it has exit.
/// ///
@@ -55,40 +43,29 @@ pub trait EventLoopExtRunOnDemand {
/// - Android /// - Android
/// ///
/// # Unsupported Platforms /// # Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which /// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// Web browsers work because it's not possible to have a long-running external /// browsers work because it's not possible to have a long-running external loop that would
/// loop that would block the browser and there is nothing that can be /// block the browser and there is nothing that can be polled to ask for new events. Events
/// polled to ask for new events. Events are delivered via callbacks based /// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// 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. /// - **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( #[rustfmt::skip]
not(web_platform),
doc = "[^1]: `spawn()` is only available on `wasm` platforms."
)]
/// ///
/// [`exit()`]: ActiveEventLoop::exit() /// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>( fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
app: &mut A, app: &mut A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.window_target().clear_exit(); self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler) self.event_loop.run_app_on_demand(app)
} }
} }

View File

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

View File

@@ -13,11 +13,9 @@
//! * `wayland-csd-adwaita` (default). //! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`. //! * `wayland-csd-adwaita-notitle`.
use crate::{ use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
event_loop::{ActiveEventLoop, EventLoopBuilder}, use crate::monitor::MonitorHandle;
monitor::MonitorHandle, use crate::window::{Window, WindowAttributes};
window::{Window, WindowAttributes},
};
pub use crate::window::Theme; 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 EventLoopExtWayland for EventLoop {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland. /// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
pub trait EventLoopBuilderExtWayland { pub trait EventLoopBuilderExtWayland {
/// Force using Wayland. /// Force using Wayland.
@@ -46,7 +57,7 @@ pub trait EventLoopBuilderExtWayland {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
} }
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> { impl EventLoopBuilderExtWayland for EventLoopBuilder {
#[inline] #[inline]
fn with_wayland(&mut self) -> &mut Self { fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland); self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
@@ -80,10 +91,8 @@ pub trait WindowAttributesExtWayland {
impl WindowAttributesExtWayland for WindowAttributes { impl WindowAttributesExtWayland for WindowAttributes {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new( self.platform_specific.name =
general.into(), Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
instance.into(),
));
self self
} }
} }

View File

@@ -30,8 +30,8 @@
//! The following APIs can't take them into account and will therefore provide inaccurate results: //! The following APIs can't take them into account and will therefore provide inaccurate results:
//! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`] //! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`]
//! - [`WindowEvent::Occluded`] //! - [`WindowEvent::Occluded`]
//! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], //! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], and
//! and [`WindowEvent::Touch`]. //! [`WindowEvent::Touch`].
//! - [`Window::set_outer_position()`] //! - [`Window::set_outer_position()`]
//! //!
//! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized //! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized
@@ -55,8 +55,7 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource; use crate::cursor::CustomCursorSource;
use crate::event::Event; use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(web_platform)] #[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture; use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource; use crate::platform_impl::PlatformCustomCursorSource;
@@ -109,11 +108,7 @@ pub trait WindowAttributesExtWebSys {
/// In any case, the canvas won't be automatically inserted into the web page. /// In any case, the canvas won't be automatically inserted into the web page.
/// ///
/// [`None`] by default. /// [`None`] by default.
#[cfg_attr( #[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")]
not(web_platform),
doc = "",
doc = "[`HtmlCanvasElement`]: #only-available-on-wasm"
)]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self; fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Sets whether `event.preventDefault()` should be called on events on the /// Sets whether `event.preventDefault()` should be called on events on the
@@ -160,16 +155,10 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web. /// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys { pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent: 'static;
/// Initializes the winit event loop. /// Initializes the winit event loop.
/// ///
/// Unlike /// Unlike
#[cfg_attr( #[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
all(web_platform, target_feature = "exception-handling"),
doc = "`run_app()`"
)]
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]" doc = "[`run_app()`]"
@@ -181,37 +170,63 @@ pub trait EventLoopExtWebSys {
/// by calling this function again. This can be useful if you want to recreate the event loop /// 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 /// 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. /// event loop when switching between tabs on a single page application.
#[rustfmt::skip]
/// ///
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]: EventLoop::run_app()" doc = "[`run_app()`]: EventLoop::run_app()"
)] )]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`. /// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A); fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// See [`spawn_app`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
/// [`spawn_app`]: Self::spawn_app /// See [`PollStrategy`].
#[deprecated = "use EventLoopExtWebSys::spawn_app"] ///
fn spawn<F>(self, event_handler: F) /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
where fn set_poll_strategy(&self, strategy: PollStrategy);
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// 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> { impl EventLoopExtWebSys for EventLoop {
type UserEvent = T; fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
} }
fn spawn<F>(self, event_handler: F) fn set_poll_strategy(&self, strategy: PollStrategy) {
where self.event_loop.set_poll_strategy(strategy);
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop), }
{
self.event_loop.spawn(event_handler) 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()
} }
} }
@@ -230,6 +245,20 @@ pub trait ActiveEventLoopExtWebSys {
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy; 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 /// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
/// cursor has completely finished loading. /// cursor has completely finished loading.
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture; fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
@@ -250,6 +279,16 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
fn poll_strategy(&self) -> PollStrategy { fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy() 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].
@@ -278,6 +317,29 @@ pub enum PollStrategy {
Scheduler, 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 { pub trait CustomCursorExtWebSys {
/// Returns if this cursor is an animation. /// Returns if this cursor is an animation.
fn is_animation(&self) -> bool; fn is_animation(&self) -> bool;
@@ -303,13 +365,7 @@ impl CustomCursorExtWebSys for CustomCursor {
} }
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource { fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource { CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
inner: PlatformCustomCursorSource::Url {
url,
hotspot_x,
hotspot_y,
},
}
} }
fn from_animation( fn from_animation(
@@ -360,9 +416,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>; type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0) Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
.poll(cx)
.map_ok(|cursor| CustomCursor { inner: cursor })
} }
} }
@@ -378,10 +432,9 @@ impl Display for CustomCursorError {
match self { match self {
Self::Blob => write!(f, "failed to create `Blob`"), Self::Blob => write!(f, "failed to create `Blob`"),
Self::Decode(error) => write!(f, "failed to decode image: {error}"), Self::Decode(error) => write!(f, "failed to decode image: {error}"),
Self::Animation => write!( Self::Animation => {
f, write!(f, "found `CustomCursor` that is an animation when building an animation")
"found `CustomCursor` that is an animation when building an animation" },
),
} }
} }
} }

View File

@@ -2,15 +2,15 @@
//! //!
//! The supported OS version is Windows 7 or higher, though Windows 10 is //! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly. //! tested regularly.
use std::{ffi::c_void, path::Path}; use std::borrow::Borrow;
use std::ffi::c_void;
use std::path::Path;
use crate::{ use crate::dpi::PhysicalSize;
dpi::PhysicalSize, use crate::event::DeviceId;
event::DeviceId, use crate::event_loop::EventLoopBuilder;
event_loop::EventLoopBuilder, use crate::monitor::MonitorHandle;
monitor::MonitorHandle, use crate::window::{BadIcon, Icon, Window, WindowAttributes};
window::{BadIcon, Icon, Window, WindowAttributes},
};
/// Window Handle type used by Win32 API /// Window Handle type used by Win32 API
pub type HWND = isize; pub type HWND = isize;
@@ -57,11 +57,11 @@ pub enum BackdropType {
pub struct Color(u32); pub struct Color(u32);
impl Color { 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 /// Use the system's default color
pub const SYSTEM_DEFAULT: Color = Color(0xFFFFFFFF); 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);
/// Create a new color from the given RGB values /// Create a new color from the given RGB values
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
@@ -105,6 +105,60 @@ pub enum CornerPreference {
RoundSmall = 3, 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. /// Additional methods on `EventLoop` that are specific to Windows.
pub trait EventLoopBuilderExtWindows { pub trait EventLoopBuilderExtWindows {
/// Whether to allow the event loop to be created off of the main thread. /// Whether to allow the event loop to be created off of the main thread.
@@ -173,7 +227,7 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static; F: FnMut(*const c_void) -> bool + 'static;
} }
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> { impl EventLoopBuilderExtWindows for EventLoopBuilder {
#[inline] #[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread; self.platform_specific.any_thread = any_thread;
@@ -202,8 +256,8 @@ pub trait WindowExtWindows {
/// ///
/// A window must be enabled before it can be activated. /// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window /// 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 /// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must
/// the owner window before destroying the dialog box. /// enable the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated. /// 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 /// 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. /// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference); 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 { impl WindowExtWindows for Window {
@@ -283,10 +391,10 @@ impl WindowExtWindows for Window {
#[inline] #[inline]
fn set_title_background_color(&self, color: Option<Color>) { 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 // The windows docs don't mention NONE as a valid options but it works in practice and is
// to circumvent the Windows option "Show accent color on title bars and window borders" // useful to circumvent the Windows option "Show accent color on title bars and
self.window // window borders"
.set_title_background_color(color.unwrap_or(Color::NONE)) self.window.set_title_background_color(color.unwrap_or(Color::NONE))
} }
#[inline] #[inline]
@@ -298,15 +406,57 @@ impl WindowExtWindows for Window {
fn set_corner_preference(&self, preference: CornerPreference) { fn set_corner_preference(&self, preference: CornerPreference) {
self.window.set_corner_preference(preference) 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. /// Additional methods on `WindowAttributes` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)] #[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowAttributesExtWindows { pub trait WindowAttributesExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// 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`. /// 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] /// Can be used in combination with
/// on the owner window to create a modal dialog box. /// [`WindowExtWindows::set_enable(false)`][WindowExtWindows::set_enable] on the owner
/// window to create a modal dialog box.
/// ///
/// From MSDN: /// From MSDN:
/// - An owned window is always above its owner in the z-order. /// - 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. /// 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. /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how
/// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. /// 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( #[cfg_attr(
platform_windows, windows_platform,
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu" doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)] )]
#[cfg_attr( #[cfg_attr(not(windows_platform), doc = "[`CreateMenu`]: #only-available-on-windows")]
not(platform_windows),
doc = "[`CreateMenu`]: #only-available-on-windows"
)]
fn with_menu(self, menu: HMENU) -> Self; fn with_menu(self, menu: HMENU) -> Self;
/// This sets `ICON_BIG`. A good ceiling here is 256x256. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
@@ -341,12 +488,12 @@ pub trait WindowAttributesExtWindows {
/// This sets `WS_EX_NOREDIRECTIONBITMAP`. /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> Self; fn with_no_redirection_bitmap(self, flag: bool) -> Self;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other crates /// Enables or disables drag and drop support (enabled by default). Will interfere with other
/// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of /// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED`
/// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize /// instead of `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still
/// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. /// attempt to initialize COM API regardless of this option. Currently only fullscreen mode
/// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. /// does that, but there may be more in the future. If you need COM API with
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information. /// `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; fn with_drag_and_drop(self, flag: bool) -> Self;
/// Whether show or hide the window icon in the taskbar. /// Whether show or hide the window icon in the taskbar.

View File

@@ -2,11 +2,9 @@
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
event_loop::{ActiveEventLoop, EventLoopBuilder}, use crate::monitor::MonitorHandle;
monitor::MonitorHandle, use crate::window::{Window, WindowAttributes};
window::{Window, WindowAttributes},
};
use crate::dpi::Size; use crate::dpi::Size;
@@ -15,11 +13,12 @@ use crate::dpi::Size;
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum WindowType { pub enum WindowType {
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the /// A desktop feature. This can include a single window containing desktop icons with the same
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying /// dimensions as the screen, allowing the desktop environment to have full control of the
/// root window clicks. /// desktop, without the need for proxying root window clicks.
Desktop, 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, Dock,
/// Toolbar windows. "Torn off" from the main application. /// Toolbar windows. "Torn off" from the main application.
Toolbar, Toolbar,
@@ -37,8 +36,8 @@ pub enum WindowType {
/// A popup menu that usually appears when the user right clicks on an object. /// A popup menu that usually appears when the user right clicks on an object.
/// This property is typically used on override-redirect windows. /// This property is typically used on override-redirect windows.
PopupMenu, PopupMenu,
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor. /// A tooltip window. Usually used to show additional information when hovering over an object
/// This property is typically used on override-redirect windows. /// with the cursor. This property is typically used on override-redirect windows.
Tooltip, Tooltip,
/// The window is a notification. /// The window is a notification.
/// This property is typically used on override-redirect windows. /// 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 should be used on the windows that are popped up by combo boxes.
/// This property is typically used on override-redirect windows. /// This property is typically used on override-redirect windows.
Combo, 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. /// This property is typically used on override-redirect windows.
Dnd, Dnd,
/// This is a normal, top-level window. /// This is a normal, top-level window.
@@ -83,10 +82,7 @@ pub type XWindow = u32;
pub fn register_xlib_error_hook(hook: XlibErrorHook) { pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook. // Append new hook.
unsafe { unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
.lock()
.unwrap()
.push(hook);
} }
} }
@@ -103,6 +99,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 EventLoopExtX11 for EventLoop {
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
}
}
/// Additional methods on [`EventLoopBuilder`] that are specific to X11. /// Additional methods on [`EventLoopBuilder`] that are specific to X11.
pub trait EventLoopBuilderExtX11 { pub trait EventLoopBuilderExtX11 {
/// Force using X11. /// Force using X11.
@@ -115,7 +124,7 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
} }
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> { impl EventLoopBuilderExtX11 for EventLoopBuilder {
#[inline] #[inline]
fn with_x11(&mut self) -> &mut Self { fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X); self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
@@ -144,7 +153,8 @@ pub trait WindowAttributesExtX11 {
/// Build window with the given `general` and `instance` names. /// Build window with the given `general` and `instance` names.
/// ///
/// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the /// 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 /// 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) /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
@@ -202,10 +212,8 @@ impl WindowAttributesExtX11 for WindowAttributes {
#[inline] #[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self { fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name = Some(crate::platform_impl::ApplicationName::new( self.platform_specific.name =
general.into(), Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
instance.into(),
));
self self
} }

View File

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

View File

@@ -1,16 +1,10 @@
#![cfg(android_platform)] use std::cell::Cell;
use std::collections::VecDeque;
use std::{ use std::hash::Hash;
cell::Cell, use std::marker::PhantomData;
collections::VecDeque, use std::sync::atomic::{AtomicBool, Ordering};
hash::Hash, use std::sync::{Arc, Mutex};
marker::PhantomData, use std::time::{Duration, Instant};
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, Mutex,
},
time::{Duration, Instant},
};
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{ use android_activity::{
@@ -18,24 +12,25 @@ use android_activity::{
}; };
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use crate::{ use crate::application::ApplicationHandler;
cursor::Cursor, use crate::cursor::Cursor;
dpi::{PhysicalPosition, PhysicalSize, Position, Size}, use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
error, use crate::error;
event::{self, Force, InnerSizeWriter, StartCause}, use crate::error::EventLoopError;
event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents}, use crate::event::{self, Force, InnerSizeWriter, StartCause};
platform::pump_events::PumpStatus, use crate::event_loop::{self, ControlFlow, DeviceEvents};
window::{ use crate::platform::pump_events::PumpStatus;
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme, use crate::platform_impl::Fullscreen;
WindowButtons, WindowLevel, use crate::window::{
}, self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
WindowButtons, WindowLevel,
}; };
use crate::{error::EventLoopError, platform_impl::Fullscreen};
mod keycodes; mod keycodes;
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursor; pub(crate) use crate::cursor::{
pub(crate) use crate::cursor::NoCustomCursor as PlatformCustomCursorSource; NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true); static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@@ -44,42 +39,7 @@ static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
/// equates to an infinite timeout, not a zero timeout (so can't just use /// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`) /// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> { fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| { a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))
})
}
struct PeekableReceiver<T> {
recv: mpsc::Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: mpsc::Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
}
Err(mpsc::TryRecvError::Empty) => false,
Err(mpsc::TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
}
}
}
pub fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
} }
#[derive(Clone)] #[derive(Clone)]
@@ -88,9 +48,7 @@ struct SharedFlagSetter {
} }
impl SharedFlagSetter { impl SharedFlagSetter {
pub fn set(&self) -> bool { pub fn set(&self) -> bool {
self.flag self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
} }
} }
@@ -104,15 +62,13 @@ struct SharedFlag {
// was queued and be able to read and clear the state atomically) // was queued and be able to read and clear the state atomically)
impl SharedFlag { impl SharedFlag {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self { flag: Arc::new(AtomicBool::new(false)) }
flag: Arc::new(AtomicBool::new(false)),
}
} }
pub fn setter(&self) -> SharedFlagSetter { pub fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { SharedFlagSetter { flag: self.flag.clone() }
flag: self.flag.clone(),
}
} }
pub fn get_and_reset(&self) -> bool { pub fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel) self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
} }
@@ -126,11 +82,9 @@ pub struct RedrawRequester {
impl RedrawRequester { impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester { RedrawRequester { flag: flag.setter(), waker }
flag: flag.setter(),
waker,
}
} }
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
if self.flag.set() { if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag // Only explicitly try to wake up the main loop when the flag
@@ -143,13 +97,12 @@ impl RedrawRequester {
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {} pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> { pub struct EventLoop {
android_app: AndroidApp, android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop, window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>, proxy_wake_up: Arc<AtomicBool>,
user_events_receiver: PeekableReceiver<T>, //must wake looper whenever something gets sent loop_running: bool, // Dispatched `NewEvents<Init>`
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool, running: bool,
pending_redraw: bool, pending_redraw: bool,
cause: StartCause, cause: StartCause,
@@ -165,20 +118,20 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self { fn default() -> Self {
Self { Self { android_app: Default::default(), ignore_volume_keys: true }
android_app: Default::default(),
ignore_volume_keys: true,
}
} }
} }
impl<T: 'static> EventLoop<T> { impl EventLoop {
pub(crate) fn new( pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes, attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> { ) -> Result<Self, EventLoopError> {
let (user_events_sender, user_events_receiver) = mpsc::channel(); let proxy_wake_up = Arc::new(AtomicBool::new(false));
let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android"); let 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(); let redraw_flag = SharedFlag::new();
Ok(Self { Ok(Self {
@@ -196,8 +149,7 @@ impl<T: 'static> EventLoop<T> {
_marker: PhantomData, _marker: PhantomData,
}, },
redraw_flag, redraw_flag,
user_events_sender, proxy_wake_up,
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
loop_running: false, loop_running: false,
running: false, running: false,
pending_redraw: false, pending_redraw: false,
@@ -207,53 +159,46 @@ impl<T: 'static> EventLoop<T> {
}) })
} }
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F) fn single_iteration<A: ApplicationHandler>(
where &mut self,
F: FnMut(event::Event<T>, &RootAEL), main_event: Option<MainEvent<'_>>,
{ app: &mut A,
) {
trace!("Mainloop iteration"); trace!("Mainloop iteration");
let cause = self.cause; let cause = self.cause;
let mut pending_redraw = self.pending_redraw; let mut pending_redraw = self.pending_redraw;
let mut resized = false; let mut resized = false;
callback(event::Event::NewEvents(cause), self.window_target()); app.new_events(self.window_target(), cause);
if let Some(event) = main_event { if let Some(event) = main_event {
trace!("Handling main event {:?}", event); trace!("Handling main event {:?}", event);
match event { match event {
MainEvent::InitWindow { .. } => { MainEvent::InitWindow { .. } => {
callback(event::Event::Resumed, self.window_target()); app.resumed(self.window_target());
} },
MainEvent::TerminateWindow { .. } => { MainEvent::TerminateWindow { .. } => {
callback(event::Event::Suspended, self.window_target()); app.suspended(self.window_target());
} },
MainEvent::WindowResized { .. } => resized = true, MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true, MainEvent::RedrawNeeded { .. } => pending_redraw = true,
MainEvent::ContentRectChanged { .. } => { MainEvent::ContentRectChanged { .. } => {
warn!("TODO: find a way to notify application of content rect change"); warn!("TODO: find a way to notify application of content rect change");
} },
MainEvent::GainedFocus => { MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed); HAS_FOCUS.store(true, Ordering::Relaxed);
callback( let window_id = window::WindowId(WindowId);
event::Event::WindowEvent { let event = event::WindowEvent::Focused(true);
window_id: window::WindowId(WindowId), app.window_event(self.window_target(), window_id, event);
event: event::WindowEvent::Focused(true), },
},
self.window_target(),
);
}
MainEvent::LostFocus => { MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed); HAS_FOCUS.store(false, Ordering::Relaxed);
callback( let window_id = window::WindowId(WindowId);
event::Event::WindowEvent { let event = event::WindowEvent::Focused(false);
window_id: window::WindowId(WindowId), app.window_event(self.window_target(), window_id, event);
event: event::WindowEvent::Focused(false), },
},
self.window_target(),
);
}
MainEvent::ConfigChanged { .. } => { MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone()); let monitor = MonitorHandle::new(self.android_app.clone());
let old_scale_factor = monitor.scale_factor(); let old_scale_factor = monitor.scale_factor();
@@ -262,54 +207,53 @@ impl<T: 'static> EventLoop<T> {
let new_inner_size = Arc::new(Mutex::new( let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(), MonitorHandle::new(self.android_app.clone()).size(),
)); ));
let event = event::Event::WindowEvent { let window_id = window::WindowId(WindowId);
window_id: window::WindowId(WindowId), let event = event::WindowEvent::ScaleFactorChanged {
event: event::WindowEvent::ScaleFactorChanged { inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &new_inner_size,
&new_inner_size, )),
)), scale_factor,
scale_factor,
},
}; };
callback(event, self.window_target());
app.window_event(self.window_target(), window_id, event);
} }
} },
MainEvent::LowMemory => { MainEvent::LowMemory => {
callback(event::Event::MemoryWarning, self.window_target()); app.memory_warning(self.window_target());
} },
MainEvent::Start => { MainEvent::Start => {
// XXX: how to forward this state to applications? // XXX: how to forward this state to applications?
warn!("TODO: forward onStart notification to application"); warn!("TODO: forward onStart notification to application");
} },
MainEvent::Resume { .. } => { MainEvent::Resume { .. } => {
debug!("App Resumed - is running"); debug!("App Resumed - is running");
self.running = true; self.running = true;
} },
MainEvent::SaveState { .. } => { MainEvent::SaveState { .. } => {
// XXX: how to forward this state to applications? // XXX: how to forward this state to applications?
// XXX: also how do we expose state restoration to apps? // XXX: also how do we expose state restoration to apps?
warn!("TODO: forward saveState notification to application"); warn!("TODO: forward saveState notification to application");
} },
MainEvent::Pause => { MainEvent::Pause => {
debug!("App Paused - stopped running"); debug!("App Paused - stopped running");
self.running = false; self.running = false;
} },
MainEvent::Stop => { MainEvent::Stop => {
// XXX: how to forward this state to applications? // XXX: how to forward this state to applications?
warn!("TODO: forward onStop notification to application"); warn!("TODO: forward onStop notification to application");
} },
MainEvent::Destroy => { MainEvent::Destroy => {
// XXX: maybe exit mainloop to drop things before being // XXX: maybe exit mainloop to drop things before being
// killed by the OS? // killed by the OS?
warn!("TODO: forward onDestroy notification to application"); warn!("TODO: forward onDestroy notification to application");
} },
MainEvent::InsetsChanged { .. } => { MainEvent::InsetsChanged { .. } => {
// XXX: how to forward this state to applications? // XXX: how to forward this state to applications?
warn!("TODO: handle Android InsetsChanged notification"); warn!("TODO: handle Android InsetsChanged notification");
} },
unknown => { unknown => {
trace!("Unknown MainEvent {unknown:?} (ignored)"); trace!("Unknown MainEvent {unknown:?} (ignored)");
} },
} }
} else { } else {
trace!("No main event to handle"); trace!("No main event to handle");
@@ -323,7 +267,7 @@ impl<T: 'static> EventLoop<T> {
match android_app.input_events_iter() { match android_app.input_events_iter() {
Ok(mut input_iter) => loop { Ok(mut input_iter) => loop {
let read_event = let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, callback)); input_iter.next(|event| self.handle_input_event(&android_app, event, app));
if !read_event { if !read_event {
break; break;
@@ -331,14 +275,11 @@ impl<T: 'static> EventLoop<T> {
}, },
Err(err) => { Err(err) => {
tracing::warn!("Failed to get input events iterator: {err:?}"); tracing::warn!("Failed to get input events iterator: {err:?}");
} },
} }
// Empty the user event buffer if self.proxy_wake_up.swap(false, Ordering::Relaxed) {
{ app.proxy_wake_up(self.window_target());
while let Ok(event) = self.user_events_receiver.try_recv() {
callback(crate::event::Event::UserEvent(event), self.window_target());
}
} }
if self.running { if self.running {
@@ -350,39 +291,32 @@ impl<T: 'static> EventLoop<T> {
} else { } else {
PhysicalSize::new(0, 0) PhysicalSize::new(0, 0)
}; };
let event = event::Event::WindowEvent { let window_id = window::WindowId(WindowId);
window_id: window::WindowId(WindowId), let event = event::WindowEvent::Resized(size);
event: event::WindowEvent::Resized(size), app.window_event(self.window_target(), window_id, event);
};
callback(event, self.window_target());
} }
pending_redraw |= self.redraw_flag.get_and_reset(); pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw { if pending_redraw {
pending_redraw = false; pending_redraw = false;
let event = event::Event::WindowEvent { let window_id = window::WindowId(WindowId);
window_id: window::WindowId(WindowId), let event = event::WindowEvent::RedrawRequested;
event: event::WindowEvent::RedrawRequested, app.window_event(self.window_target(), window_id, event);
};
callback(event, self.window_target());
} }
} }
// This is always the last event we dispatch before poll again // This is always the last event we dispatch before poll again
callback(event::Event::AboutToWait, self.window_target()); app.about_to_wait(self.window_target());
self.pending_redraw = pending_redraw; self.pending_redraw = pending_redraw;
} }
fn handle_input_event<F>( fn handle_input_event<A: ApplicationHandler>(
&mut self, &mut self,
android_app: &AndroidApp, android_app: &AndroidApp,
event: &InputEvent<'_>, event: &InputEvent<'_>,
callback: &mut F, app: &mut A,
) -> InputStatus ) -> InputStatus {
where
F: FnMut(event::Event<T>, &RootAEL),
{
let mut input_status = InputStatus::Handled; let mut input_status = InputStatus::Handled;
match event { match event {
InputEvent::MotionEvent(motion_event) => { InputEvent::MotionEvent(motion_event) => {
@@ -392,13 +326,13 @@ impl<T: 'static> EventLoop<T> {
let phase = match motion_event.action() { let phase = match motion_event.action() {
MotionAction::Down | MotionAction::PointerDown => { MotionAction::Down | MotionAction::PointerDown => {
Some(event::TouchPhase::Started) Some(event::TouchPhase::Started)
} },
MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended), MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended),
MotionAction::Move => Some(event::TouchPhase::Moved), MotionAction::Move => Some(event::TouchPhase::Moved),
MotionAction::Cancel => Some(event::TouchPhase::Cancelled), MotionAction::Cancel => Some(event::TouchPhase::Cancelled),
_ => { _ => {
None // TODO mouse events None // TODO mouse events
} },
}; };
if let Some(phase) = phase { if let Some(phase) = phase {
let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> = let pointers: Box<dyn Iterator<Item = android_activity::input::Pointer<'_>>> =
@@ -407,42 +341,43 @@ impl<T: 'static> EventLoop<T> {
Box::new(std::iter::once( Box::new(std::iter::once(
motion_event.pointer_at_index(motion_event.pointer_index()), motion_event.pointer_at_index(motion_event.pointer_index()),
)) ))
} },
event::TouchPhase::Moved | event::TouchPhase::Cancelled => { event::TouchPhase::Moved | event::TouchPhase::Cancelled => {
Box::new(motion_event.pointers()) Box::new(motion_event.pointers())
} },
}; };
for pointer in pointers { for pointer in pointers {
let location = PhysicalPosition { let location =
x: pointer.x() as _, PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _ };
y: pointer.y() as _, trace!(
}; "Input event {device_id:?}, {phase:?}, loc={location:?}, \
trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}"); pointer={pointer:?}"
let event = event::Event::WindowEvent { );
window_id,
event: event::WindowEvent::Touch(event::Touch { let event = event::WindowEvent::Touch(event::Touch {
device_id, device_id,
phase, phase,
location, location,
id: pointer.pointer_id() as u64, id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)), force: Some(Force::Normalized(pointer.pressure() as f64)),
}), });
};
callback(event, self.window_target()); app.window_event(self.window_target(), window_id, event);
} }
} }
} },
InputEvent::KeyEvent(key) => { InputEvent::KeyEvent(key) => {
match key.key_code() { match key.key_code() {
// Flag keys related to volume as unhandled. While winit does not have a way for applications // Flag keys related to volume as unhandled. While winit does not have a way for
// to configure what keys to flag as handled, this appears to be a good default until winit // applications to configure what keys to flag as handled,
// this appears to be a good default until winit
// can be configured. // can be configured.
Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => { Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute
if self.ignore_volume_keys { if self.ignore_volume_keys =>
input_status = InputStatus::Unhandled {
} input_status = InputStatus::Unhandled
} },
keycode => { keycode => {
let state = match key.action() { let state = match key.action() {
KeyAction::Down => event::ElementState::Pressed, KeyAction::Down => event::ElementState::Pressed,
@@ -456,64 +391,61 @@ impl<T: 'static> EventLoop<T> {
&mut self.combining_accent, &mut self.combining_accent,
); );
let event = event::Event::WindowEvent { let window_id = window::WindowId(WindowId);
window_id: window::WindowId(WindowId), let event = event::WindowEvent::KeyboardInput {
event: event::WindowEvent::KeyboardInput { device_id: event::DeviceId(DeviceId(key.device_id())),
device_id: event::DeviceId(DeviceId(key.device_id())), event: event::KeyEvent {
event: event::KeyEvent { state,
state, physical_key: keycodes::to_physical_key(keycode),
physical_key: keycodes::to_physical_key(keycode), logical_key: keycodes::to_logical(key_char, keycode),
logical_key: keycodes::to_logical(key_char, keycode), location: keycodes::to_location(keycode),
location: keycodes::to_location(keycode), repeat: key.repeat_count() > 0,
repeat: key.repeat_count() > 0, text: None,
text: None, platform_specific: KeyEventExtra {},
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
}, },
is_synthetic: false,
}; };
callback(event, self.window_target());
} app.window_event(self.window_target(), window_id, event);
},
} }
} },
_ => { _ => {
warn!("Unknown android_activity input event {event:?}") warn!("Unknown android_activity input event {event:?}")
} },
} }
input_status input_status
} }
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError> pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
where self.run_app_on_demand(app)
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
self.run_on_demand(event_handler)
} }
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError> pub fn run_app_on_demand<A: ApplicationHandler>(
where &mut self,
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop), app: &mut A,
{ ) -> Result<(), EventLoopError> {
loop { loop {
match self.pump_events(None, &mut event_handler) { match self.pump_app_events(None, app) {
PumpStatus::Exit(0) => { PumpStatus::Exit(0) => {
break Ok(()); break Ok(());
} },
PumpStatus::Exit(code) => { PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code)); break Err(EventLoopError::ExitFailure(code));
} },
_ => { _ => {
continue; continue;
} },
} }
} }
} }
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus pub fn pump_app_events<A: ApplicationHandler>(
where &mut self,
F: FnMut(event::Event<T>, &RootAEL), timeout: Option<Duration>,
{ app: &mut A,
) -> PumpStatus {
if !self.loop_running { if !self.loop_running {
self.loop_running = true; self.loop_running = true;
@@ -524,18 +456,18 @@ impl<T: 'static> EventLoop<T> {
self.cause = StartCause::Init; self.cause = StartCause::Init;
// run the initial loop iteration // run the initial loop iteration
self.single_iteration(None, &mut callback); self.single_iteration(None, app);
} }
// Consider the possibility that the `StartCause::Init` iteration could // Consider the possibility that the `StartCause::Init` iteration could
// request to Exit // request to Exit
if !self.exiting() { if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback); self.poll_events_with_timeout(timeout, app);
} }
if self.exiting() { if self.exiting() {
self.loop_running = false; self.loop_running = false;
callback(event::Event::LoopExiting, self.window_target()); app.exiting(self.window_target());
PumpStatus::Exit(0) PumpStatus::Exit(0)
} else { } else {
@@ -543,81 +475,75 @@ impl<T: 'static> EventLoop<T> {
} }
} }
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F) fn poll_events_with_timeout<A: ApplicationHandler>(
where &mut self,
F: FnMut(event::Event<T>, &RootAEL), mut timeout: Option<Duration>,
{ app: &mut A,
) {
let start = Instant::now(); let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset(); self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout = timeout = if self.running
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) { && (self.pending_redraw || self.proxy_wake_up.load(Ordering::Relaxed))
// If we already have work to do then we don't want to block on the next poll {
Some(Duration::ZERO) // If we already have work to do then we don't want to block on the next poll
} else { Some(Duration::ZERO)
let control_flow_timeout = match self.control_flow() { } else {
ControlFlow::Wait => None, let control_flow_timeout = match self.control_flow() {
ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::Wait => None,
ControlFlow::WaitUntil(wait_deadline) => { ControlFlow::Poll => Some(Duration::ZERO),
Some(wait_deadline.saturating_duration_since(start)) ControlFlow::WaitUntil(wait_deadline) => {
} Some(wait_deadline.saturating_duration_since(start))
}; },
min_timeout(control_flow_timeout, timeout)
}; };
let app = self.android_app.clone(); // Don't borrow self as part of poll expression min_timeout(control_flow_timeout, timeout)
app.poll_events(timeout, |poll_event| { };
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
let mut main_event = None; let mut main_event = None;
match poll_event { match poll_event {
android_activity::PollEvent::Wake => { android_activity::PollEvent::Wake => {
// In the X11 backend it's noted that too many false-positive wake ups // 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 // would cause the event loop to run continuously. They handle this by
// for pending events (assuming they cover all valid reasons for a wake up). // 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 // 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. // a wake up here so we can ignore the wake up if there are no events/requests.
// We also ignore wake ups while suspended. // We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset(); self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running if !self.running
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming()) || (!self.pending_redraw && !self.proxy_wake_up.load(Ordering::Relaxed))
{ {
return; return;
} }
} },
android_activity::PollEvent::Timeout => {} android_activity::PollEvent::Timeout => {},
android_activity::PollEvent::Main(event) => { android_activity::PollEvent::Main(event) => {
main_event = Some(event); main_event = Some(event);
} },
unknown_event => { unknown_event => {
warn!("Unknown poll event {unknown_event:?} (ignored)"); warn!("Unknown poll event {unknown_event:?} (ignored)");
} },
} }
self.cause = match self.control_flow() { self.cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll, ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
start,
requested_resume: None,
},
ControlFlow::WaitUntil(deadline) => { ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline { if Instant::now() < deadline {
StartCause::WaitCancelled { StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
start,
requested_resume: Some(deadline),
}
} else { } else {
StartCause::ResumeTimeReached { StartCause::ResumeTimeReached { start, requested_resume: deadline }
start,
requested_resume: deadline,
}
} }
} },
}; };
self.single_iteration(main_event, &mut callback); self.single_iteration(main_event, app);
}); });
} }
@@ -625,9 +551,9 @@ impl<T: 'static> EventLoop<T> {
&self.window_target &self.window_target
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { EventLoopProxy {
user_events_sender: self.user_events_sender.clone(), proxy_wake_up: self.proxy_wake_up.clone(),
waker: self.android_app.create_waker(), waker: self.android_app.create_waker(),
} }
} }
@@ -641,27 +567,16 @@ impl<T: 'static> EventLoop<T> {
} }
} }
pub struct EventLoopProxy<T: 'static> { #[derive(Clone)]
user_events_sender: mpsc::Sender<T>, pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
waker: AndroidAppWaker, waker: AndroidAppWaker,
} }
impl<T: 'static> Clone for EventLoopProxy<T> { impl EventLoopProxy {
fn clone(&self) -> Self { pub fn wake_up(&self) {
EventLoopProxy { self.proxy_wake_up.store(true, Ordering::Relaxed);
user_events_sender: self.user_events_sender.clone(),
waker: self.waker.clone(),
}
}
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|err| event_loop::EventLoopClosed(err.0))?;
self.waker.wake(); self.waker.wake();
Ok(())
} }
} }
@@ -679,9 +594,7 @@ impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner; let _ = source.inner;
CustomCursor { CustomCursor { inner: PlatformCustomCursor }
inner: PlatformCustomCursor,
}
} }
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
@@ -704,9 +617,7 @@ impl ActiveEventLoop {
pub fn raw_display_handle_rwh_06( pub fn raw_display_handle_rwh_06(
&self, &self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android( Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
rwh_06::AndroidDisplayHandle::new(),
))
} }
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -798,10 +709,7 @@ impl Window {
) -> Result<Self, error::OsError> { ) -> Result<Self, error::OsError> {
// FIXME this ignores requested window attributes // FIXME this ignores requested window attributes
Ok(Self { Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
app: el.app.clone(),
redraw_requester: el.redraw_requester.clone(),
})
} }
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
@@ -941,41 +849,31 @@ impl Window {
pub fn set_cursor(&self, _: Cursor) {} pub fn set_cursor(&self, _: Cursor) {}
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
error::NotSupportedError::new(),
))
} }
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> { pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
error::NotSupportedError::new(),
))
} }
pub fn set_cursor_visible(&self, _: bool) {} pub fn set_cursor_visible(&self, _: bool) {}
pub fn drag_window(&self) -> Result<(), error::ExternalError> { pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
error::NotSupportedError::new(),
))
} }
pub fn drag_resize_window( pub fn drag_resize_window(
&self, &self,
_direction: ResizeDirection, _direction: ResizeDirection,
) -> Result<(), error::ExternalError> { ) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
error::NotSupportedError::new(),
))
} }
#[inline] #[inline]
pub fn show_window_menu(&self, _position: Position) {} pub fn show_window_menu(&self, _position: Position) {}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(error::NotSupportedError::new()))
error::NotSupportedError::new(),
))
} }
#[cfg(feature = "rwh_04")] #[cfg(feature = "rwh_04")]
@@ -985,7 +883,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() { if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle() native_window.raw_window_handle()
} else { } 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 +898,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() { if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle() native_window.raw_window_handle()
} else { } 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 +920,11 @@ impl Window {
if let Some(native_window) = self.app.native_window().as_ref() { if let Some(native_window) = self.app.native_window().as_ref() {
native_window.raw_window_handle() native_window.raw_window_handle()
} else { } 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) Err(rwh_06::HandleError::Unavailable)
} }
} }
@@ -1023,9 +933,7 @@ impl Window {
pub fn raw_display_handle_rwh_06( pub fn raw_display_handle_rwh_06(
&self, &self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::Android( Ok(rwh_06::RawDisplayHandle::Android(rwh_06::AndroidDisplayHandle::new()))
rwh_06::AndroidDisplayHandle::new(),
))
} }
pub fn config(&self) -> ConfigurationRef { pub fn config(&self) -> ConfigurationRef {
@@ -1102,11 +1010,7 @@ impl MonitorHandle {
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.app self.app.config().density().map(|dpi| dpi as f64 / 160.0).unwrap_or(1.0)
.config()
.density()
.map(|dpi| dpi as f64 / 160.0)
.unwrap_or(1.0)
} }
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {

View File

@@ -1,16 +1,11 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use icrate::AppKit::{
NSApplication, NSEvent, NSEventModifierFlagCommand, NSEventTypeKeyUp, NSEventTypeLeftMouseDown,
NSEventTypeLeftMouseDragged, NSEventTypeLeftMouseUp, NSEventTypeMouseMoved,
NSEventTypeOtherMouseDown, NSEventTypeOtherMouseDragged, NSEventTypeOtherMouseUp,
NSEventTypeRightMouseDown, NSEventTypeRightMouseDragged, NSEventTypeRightMouseUp, NSResponder,
};
use icrate::Foundation::{MainThreadMarker, NSObject};
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_delegate::ApplicationDelegate; use super::app_state::ApplicationDelegate;
use super::event::flags_contains; use super::DEVICE_ID;
use crate::event::{DeviceEvent, ElementState}; use crate::event::{DeviceEvent, ElementState};
declare_class!( declare_class!(
@@ -36,8 +31,8 @@ declare_class!(
// but that doesn't really matter here. // but that doesn't really matter here.
let event_type = unsafe { event.r#type() }; let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() }; let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventTypeKeyUp if event_type == NSEventType::KeyUp
&& flags_contains(modifier_flags, NSEventModifierFlagCommand) && modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{ {
if let Some(key_window) = self.keyWindow() { if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event); key_window.sendEvent(event);
@@ -55,45 +50,57 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
let event_type = unsafe { event.r#type() }; let event_type = unsafe { event.r#type() };
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match event_type { match event_type {
NSEventTypeMouseMoved NSEventType::MouseMoved
| NSEventTypeLeftMouseDragged | NSEventType::LeftMouseDragged
| NSEventTypeOtherMouseDragged | NSEventType::OtherMouseDragged
| NSEventTypeRightMouseDragged => { | NSEventType::RightMouseDragged => {
let delta_x = unsafe { event.deltaX() } as f64; let delta_x = unsafe { event.deltaX() } as f64;
let delta_y = unsafe { event.deltaY() } as f64; let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 { if delta_x != 0.0 {
delegate.queue_device_event(DeviceEvent::Motion { delegate.maybe_queue_with_handler(move |app, event_loop| {
axis: 0, app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
value: delta_x, axis: 0,
value: delta_x,
});
}); });
} }
if delta_y != 0.0 { if delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::Motion { delegate.maybe_queue_with_handler(move |app, event_loop| {
axis: 1, app.device_event(event_loop, DEVICE_ID, DeviceEvent::Motion {
value: delta_y, axis: 1,
value: delta_y,
});
}) })
} }
if delta_x != 0.0 || delta_y != 0.0 { if delta_x != 0.0 || delta_y != 0.0 {
delegate.queue_device_event(DeviceEvent::MouseMotion { delegate.maybe_queue_with_handler(move |app, event_loop| {
delta: (delta_x, delta_y), app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
}); });
} }
} },
NSEventTypeLeftMouseDown | NSEventTypeRightMouseDown | NSEventTypeOtherMouseDown => { NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
delegate.queue_device_event(DeviceEvent::Button { let button = unsafe { event.buttonNumber() } as u32;
button: unsafe { event.buttonNumber() } as u32, delegate.maybe_queue_with_handler(move |app, event_loop| {
state: ElementState::Pressed, app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
}); });
} },
NSEventTypeLeftMouseUp | NSEventTypeRightMouseUp | NSEventTypeOtherMouseUp => { NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
delegate.queue_device_event(DeviceEvent::Button { let button = unsafe { event.buttonNumber() } as u32;
button: unsafe { event.buttonNumber() } as u32, delegate.maybe_queue_with_handler(move |app, event_loop| {
state: ElementState::Released, app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Released,
});
}); });
} },
_ => (), _ => (),
} }
} }

View File

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

View File

@@ -2,24 +2,23 @@ use std::ffi::c_uchar;
use std::slice; use std::slice;
use std::sync::OnceLock; use std::sync::OnceLock;
use icrate::AppKit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2::rc::Retained;
use icrate::Foundation::{ use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString, NSString,
}; };
use objc2::rc::Id;
use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use crate::cursor::CursorImage; use crate::cursor::{CursorImage, OnlyCursorImageSource};
use crate::cursor::OnlyCursorImageSource;
use crate::window::CursorIcon; use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Id<NSCursor>); pub struct CustomCursor(pub(crate) Retained<NSCursor>);
// SAFETY: NSCursor is immutable and thread-safe // SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in icrate itself // TODO(madsmtm): Put this logic in objc2-app-kit itself
unsafe impl Send for CustomCursor {} unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {} unsafe impl Sync for CustomCursor {}
@@ -29,7 +28,7 @@ impl CustomCursor {
} }
} }
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> { pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Retained<NSCursor> {
let width = cursor.width; let width = cursor.width;
let height = cursor.height; let height = cursor.height;
@@ -61,14 +60,14 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot) NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
} }
pub(crate) fn default_cursor() -> Id<NSCursor> { pub(crate) fn default_cursor() -> Retained<NSCursor> {
NSCursor::arrowCursor() NSCursor::arrowCursor()
} }
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Id<NSCursor>> { unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class(); let cls = NSCursor::class();
if cls.responds_to(sel) { if cls.responds_to(sel) {
let cursor: Id<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] }; let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor) Some(cursor)
} else { } else {
tracing::warn!("cursor `{sel}` appears to be invalid"); tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -83,7 +82,7 @@ macro_rules! def_undocumented_cursor {
)*} => {$( )*} => {$(
$(#[$($m)*])* $(#[$($m)*])*
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn $name() -> Id<NSCursor> { fn $name() -> Retained<NSCursor> {
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) } unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
} }
)*}; )*};
@@ -113,12 +112,15 @@ def_undocumented_cursor!(
// Note that loading `busybutclickable` with this code won't animate // Note that loading `busybutclickable` with this code won't animate
// the frames; instead you'll just get them all in a column. // the frames; instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> { unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// Snatch a cursor from WebKit; They fit the style of the native // Snatch a cursor from WebKit; They fit the style of the native
// cursors, and will seem completely standard to macOS users. // cursors, and will seem completely standard to macOS users.
// //
// https://stackoverflow.com/a/21786835/5435443 // https://stackoverflow.com/a/21786835/5435443
let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"); let root = ns_string!(
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/\
HIServices.framework/Versions/A/Resources/cursors"
);
let cursor_path = root.stringByAppendingPathComponent(name); let cursor_path = root.stringByAppendingPathComponent(name);
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf")); let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
@@ -126,7 +128,7 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
// TODO: Handle PLists better // TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe { let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![ msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(), <NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path, dictionaryWithContentsOfFile: &*info_path,
@@ -153,26 +155,26 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot) NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
} }
fn webkit_move() -> Id<NSCursor> { fn webkit_move() -> Retained<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("move")) } unsafe { load_webkit_cursor(ns_string!("move")) }
} }
fn webkit_cell() -> Id<NSCursor> { fn webkit_cell() -> Retained<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("cell")) } unsafe { load_webkit_cursor(ns_string!("cell")) }
} }
pub(crate) fn invisible_cursor() -> Id<NSCursor> { pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
// 16x16 GIF data for invisible cursor // 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick. // You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif // $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[ static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0e, 0x84, 0x8f, 0xa9, 0xcb, 0xed, 0x0f,
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, 0xa3, 0x9c, 0xb4, 0xda, 0x8b, 0xb3, 0x3e, 0x05, 0x00, 0x3b,
]; ];
fn new_invisible() -> Id<NSCursor> { fn new_invisible() -> Retained<NSCursor> {
// TODO: Consider using `dataWithBytesNoCopy:` // TODO: Consider using `dataWithBytesNoCopy:`
let data = NSData::with_bytes(CURSOR_BYTES); let data = NSData::with_bytes(CURSOR_BYTES);
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap(); let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
@@ -182,13 +184,10 @@ pub(crate) fn invisible_cursor() -> Id<NSCursor> {
// Cache this for efficiency // Cache this for efficiency
static CURSOR: OnceLock<CustomCursor> = OnceLock::new(); static CURSOR: OnceLock<CustomCursor> = OnceLock::new();
CURSOR CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
.get_or_init(|| CustomCursor(new_invisible()))
.0
.clone()
} }
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Id<NSCursor> { pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon { match icon {
CursorIcon::Default => default_cursor(), CursorIcon::Default => default_cursor(),
CursorIcon::Pointer => NSCursor::pointingHandCursor(), CursorIcon::Pointer => NSCursor::pointingHandCursor(),

View File

@@ -1,25 +1,17 @@
use std::ffi::c_void; use std::ffi::c_void;
use core_foundation::{ use core_foundation::base::CFRelease;
base::CFRelease, use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
data::{CFDataGetBytePtr, CFDataRef}, use objc2::rc::Retained;
}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use icrate::AppKit::{ use objc2_foundation::{run_on_main, NSPoint};
NSEvent, NSEventModifierFlagCommand, NSEventModifierFlagControl, NSEventModifierFlagOption,
NSEventModifierFlagShift, NSEventModifierFlags, NSEventSubtypeWindowExposed,
NSEventTypeApplicationDefined,
};
use icrate::Foundation::{MainThreadMarker, NSPoint};
use objc2::rc::Id;
use smol_str::SmolStr; use smol_str::SmolStr;
use crate::{ use super::ffi;
event::{ElementState, KeyEvent, Modifiers}, use crate::event::{ElementState, KeyEvent, Modifiers};
keyboard::{ use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
NativeKeyCode, PhysicalKey, PhysicalKey,
},
platform_impl::platform::ffi,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -48,7 +40,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
} }
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
} }
let keyboard_type = MainThreadMarker::run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0; let mut result_len = 0;
let mut dead_keys = 0; let mut dead_keys = 0;
@@ -71,10 +63,7 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
CFRelease(input_source as *mut c_void); CFRelease(input_source as *mut c_void);
} }
if translate_result != 0 { if translate_result != 0 {
tracing::error!( tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
"`UCKeyTranslate` returned with the non-zero value: {}",
translate_result
);
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
} }
if result_len == 0 { if result_len == 0 {
@@ -125,9 +114,8 @@ pub(crate) fn create_key_event(
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() { let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
None None
} else { } else {
let characters = unsafe { ns_event.characters() } let characters =
.map(|s| s.to_string()) unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
.unwrap_or_default();
if characters.is_empty() { if characters.is_empty() {
None None
} else { } else {
@@ -145,8 +133,8 @@ pub(crate) fn create_key_event(
let key_without_modifiers = get_modifierless_char(scancode); let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() }; let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = flags_contains(modifiers, NSEventModifierFlagControl); let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
let has_cmd = flags_contains(modifiers, NSEventModifierFlagCommand); let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
let logical_key = match text_with_all_modifiers.as_ref() { let logical_key = match text_with_all_modifiers.as_ref() {
// Only checking for ctrl and cmd here, not checking for alt because we DO want to // Only checking for ctrl and cmd here, not checking for alt because we DO want to
@@ -157,7 +145,7 @@ pub(crate) fn create_key_event(
Some(text) if !has_ctrl && !has_cmd => { Some(text) if !has_ctrl && !has_cmd => {
// Character heeding both SHIFT and ALT. // Character heeding both SHIFT and ALT.
Key::Character(text.clone()) Key::Character(text.clone())
} },
_ => match key_without_modifiers.as_ref() { _ => match key_without_modifiers.as_ref() {
// Character heeding just SHIFT, ignoring ALT. // Character heeding just SHIFT, ignoring ALT.
@@ -173,11 +161,7 @@ pub(crate) fn create_key_event(
(key_from_code.clone(), key_from_code) (key_from_code.clone(), key_from_code)
}; };
let text = if is_press { let text = if is_press { logical_key.to_text().map(SmolStr::new) } else { None };
logical_key.to_text().map(SmolStr::new)
} else {
None
};
let location = code_to_location(physical_key); let location = code_to_location(physical_key);
@@ -188,10 +172,7 @@ pub(crate) fn create_key_event(
repeat: is_repeat, repeat: is_repeat,
state, state,
text, text,
platform_specific: KeyEventExtra { platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
text_with_all_modifiers,
key_without_modifiers,
},
} }
} }
@@ -315,25 +296,21 @@ pub fn extra_function_key_to_code(scancode: u16, string: &str) -> PhysicalKey {
} }
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259 // The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = 0x00000001; const NX_DEVICELCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000001);
const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = 0x00000002; const NX_DEVICELSHIFTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000002);
const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = 0x00000004; const NX_DEVICERSHIFTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000004);
const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = 0x00000008; const NX_DEVICELCMDKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000008);
const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = 0x00000010; const NX_DEVICERCMDKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000010);
const NX_DEVICELALTKEYMASK: NSEventModifierFlags = 0x00000020; const NX_DEVICELALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000020);
const NX_DEVICERALTKEYMASK: NSEventModifierFlags = 0x00000040; const NX_DEVICERALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000040);
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = 0x00002000; const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000);
pub(super) fn flags_contains(flags: NSEventModifierFlags, value: NSEventModifierFlags) -> bool {
flags & value == value
}
pub(super) fn lalt_pressed(event: &NSEvent) -> bool { pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICELALTKEYMASK) unsafe { event.modifierFlags() }.contains(NX_DEVICELALTKEYMASK)
} }
pub(super) fn ralt_pressed(event: &NSEvent) -> bool { pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICERALTKEYMASK) unsafe { event.modifierFlags() }.contains(NX_DEVICERALTKEYMASK)
} }
pub(super) fn event_mods(event: &NSEvent) -> Modifiers { pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
@@ -341,74 +318,42 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let mut state = ModifiersState::empty(); let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty(); let mut pressed_mods = ModifiersKeys::empty();
state.set( state
ModifiersState::SHIFT, .set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
flags_contains(flags, NSEventModifierFlagShift), pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
); pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
pressed_mods.set(
ModifiersKeys::LSHIFT,
flags_contains(flags, NX_DEVICELSHIFTKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RSHIFT,
flags_contains(flags, NX_DEVICERSHIFTKEYMASK),
);
state.set( state.set(
ModifiersState::CONTROL, ModifiersState::CONTROL,
flags_contains(flags, NSEventModifierFlagControl), flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
pressed_mods.set(
ModifiersKeys::LCONTROL,
flags_contains(flags, NX_DEVICELCTLKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RCONTROL,
flags_contains(flags, NX_DEVICERCTLKEYMASK),
); );
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
state.set( state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
ModifiersState::ALT, pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
flags_contains(flags, NSEventModifierFlagOption), pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
);
pressed_mods.set(
ModifiersKeys::LALT,
flags_contains(flags, NX_DEVICELALTKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RALT,
flags_contains(flags, NX_DEVICERALTKEYMASK),
);
state.set( state.set(
ModifiersState::SUPER, ModifiersState::SUPER,
flags_contains(flags, NSEventModifierFlagCommand), flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
pressed_mods.set(
ModifiersKeys::LSUPER,
flags_contains(flags, NX_DEVICELCMDKEYMASK),
);
pressed_mods.set(
ModifiersKeys::RSUPER,
flags_contains(flags, NX_DEVICERCMDKEYMASK),
); );
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
Modifiers { Modifiers { state, pressed_mods }
state,
pressed_mods,
}
} }
pub(super) fn dummy_event() -> Option<Id<NSEvent>> { pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
unsafe { unsafe {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2( NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventTypeApplicationDefined, NSEventType::ApplicationDefined,
NSPoint::new(0.0, 0.0), NSPoint::new(0.0, 0.0),
0, // Empty NSEventModifierFlags NSEventModifierFlags(0),
0.0, 0.0,
0, 0,
None, None,
NSEventSubtypeWindowExposed, NSEventSubtype::WindowExposed.0,
0, 0,
0, 0,
) )
@@ -549,7 +494,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x07 => KeyCode::KeyX, 0x07 => KeyCode::KeyX,
0x08 => KeyCode::KeyC, 0x08 => KeyCode::KeyC,
0x09 => KeyCode::KeyV, 0x09 => KeyCode::KeyV,
//0x0a => World 1, // 0x0a => World 1,
0x0b => KeyCode::KeyB, 0x0b => KeyCode::KeyB,
0x0c => KeyCode::KeyQ, 0x0c => KeyCode::KeyQ,
0x0d => KeyCode::KeyW, 0x0d => KeyCode::KeyW,
@@ -591,7 +536,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x31 => KeyCode::Space, 0x31 => KeyCode::Space,
0x32 => KeyCode::Backquote, 0x32 => KeyCode::Backquote,
0x33 => KeyCode::Backspace, 0x33 => KeyCode::Backspace,
//0x34 => unknown, // 0x34 => unknown,
0x35 => KeyCode::Escape, 0x35 => KeyCode::Escape,
0x36 => KeyCode::SuperRight, 0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft, 0x37 => KeyCode::SuperLeft,
@@ -605,22 +550,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x3f => KeyCode::Fn, 0x3f => KeyCode::Fn,
0x40 => KeyCode::F17, 0x40 => KeyCode::F17,
0x41 => KeyCode::NumpadDecimal, 0x41 => KeyCode::NumpadDecimal,
//0x42 -> unknown, // 0x42 -> unknown,
0x43 => KeyCode::NumpadMultiply, 0x43 => KeyCode::NumpadMultiply,
//0x44 => unknown, // 0x44 => unknown,
0x45 => KeyCode::NumpadAdd, 0x45 => KeyCode::NumpadAdd,
//0x46 => unknown, // 0x46 => unknown,
0x47 => KeyCode::NumLock, 0x47 => KeyCode::NumLock,
//0x48 => KeyCode::NumpadClear, // 0x48 => KeyCode::NumpadClear,
// TODO: (Artur) for me, kVK_VolumeUp is 0x48 // TODO: (Artur) for me, kVK_VolumeUp is 0x48
// macOS 10.11 // macOS 10.11
// /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/
// Versions/A/Headers/Events.h
0x49 => KeyCode::AudioVolumeUp, 0x49 => KeyCode::AudioVolumeUp,
0x4a => KeyCode::AudioVolumeDown, 0x4a => KeyCode::AudioVolumeDown,
0x4b => KeyCode::NumpadDivide, 0x4b => KeyCode::NumpadDivide,
0x4c => KeyCode::NumpadEnter, 0x4c => KeyCode::NumpadEnter,
//0x4d => unknown, // 0x4d => unknown,
0x4e => KeyCode::NumpadSubtract, 0x4e => KeyCode::NumpadSubtract,
0x4f => KeyCode::F18, 0x4f => KeyCode::F18,
0x50 => KeyCode::F19, 0x50 => KeyCode::F19,
@@ -637,25 +583,25 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x5b => KeyCode::Numpad8, 0x5b => KeyCode::Numpad8,
0x5c => KeyCode::Numpad9, 0x5c => KeyCode::Numpad9,
0x5d => KeyCode::IntlYen, 0x5d => KeyCode::IntlYen,
//0x5e => JIS Ro, // 0x5e => JIS Ro,
//0x5f => unknown, // 0x5f => unknown,
0x60 => KeyCode::F5, 0x60 => KeyCode::F5,
0x61 => KeyCode::F6, 0x61 => KeyCode::F6,
0x62 => KeyCode::F7, 0x62 => KeyCode::F7,
0x63 => KeyCode::F3, 0x63 => KeyCode::F3,
0x64 => KeyCode::F8, 0x64 => KeyCode::F8,
0x65 => KeyCode::F9, 0x65 => KeyCode::F9,
//0x66 => JIS Eisuu (macOS), // 0x66 => JIS Eisuu (macOS),
0x67 => KeyCode::F11, 0x67 => KeyCode::F11,
//0x68 => JIS Kanna (macOS), // 0x68 => JIS Kanna (macOS),
0x69 => KeyCode::F13, 0x69 => KeyCode::F13,
0x6a => KeyCode::F16, 0x6a => KeyCode::F16,
0x6b => KeyCode::F14, 0x6b => KeyCode::F14,
//0x6c => unknown, // 0x6c => unknown,
0x6d => KeyCode::F10, 0x6d => KeyCode::F10,
//0x6e => unknown, // 0x6e => unknown,
0x6f => KeyCode::F12, 0x6f => KeyCode::F12,
//0x70 => unknown, // 0x70 => unknown,
0x71 => KeyCode::F15, 0x71 => KeyCode::F15,
0x72 => KeyCode::Insert, 0x72 => KeyCode::Insert,
0x73 => KeyCode::Home, 0x73 => KeyCode::Home,
@@ -670,7 +616,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x7c => KeyCode::ArrowRight, 0x7c => KeyCode::ArrowRight,
0x7d => KeyCode::ArrowDown, 0x7d => KeyCode::ArrowDown,
0x7e => KeyCode::ArrowUp, 0x7e => KeyCode::ArrowUp,
//0x7f => unknown, // 0x7f => unknown,
// 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as
// backquote (`) on Windows' US layout. // backquote (`) on Windows' US layout.

View File

@@ -1,32 +1,34 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::{fmt, mem};
use std::mem;
use super::app_delegate::HandlePendingUserEvents; use crate::application::ApplicationHandler;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
struct EventHandlerData { #[derive(Default)]
#[allow(clippy::type_complexity)]
handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
}
impl fmt::Debug for EventHandlerData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventHandlerData").finish_non_exhaustive()
}
}
#[derive(Debug, Default)]
pub(crate) struct EventHandler { pub(crate) struct EventHandler {
/// This can be in the following states: /// This can be in the following states:
/// - Not registered by the event loop (None). /// - Not registered by the event loop (None).
/// - Present (Some(handler)). /// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed). /// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<EventHandlerData>>, inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
} }
impl EventHandler { impl EventHandler {
pub(crate) fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure. /// Set the event loop handler for the duration of the given closure.
/// ///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates /// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
@@ -34,7 +36,7 @@ impl EventHandler {
/// from within the closure. /// from within the closure.
pub(crate) fn set<'handler, R>( pub(crate) fn set<'handler, R>(
&self, &self,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler, app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can // SAFETY: We extend the lifetime of the handler here so that we can
@@ -45,21 +47,21 @@ impl EventHandler {
// extended beyond `'handler`. // extended beyond `'handler`.
let handler = unsafe { let handler = unsafe {
mem::transmute::< mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler>, &'handler mut dyn ApplicationHandler,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>, &'static mut dyn ApplicationHandler,
>(Box::new(handler)) >(app)
}; };
match self.inner.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(_)) => { Ok(Some(_)) => {
unreachable!("tried to set handler while another was already set"); unreachable!("tried to set handler while another was already set");
} },
Ok(data @ None) => { Ok(data @ None) => {
*data = Some(EventHandlerData { handler }); *data = Some(handler);
} },
Err(_) => { Err(_) => {
unreachable!("tried to set handler that is currently in use"); unreachable!("tried to set handler that is currently in use");
} },
} }
struct ClearOnDrop<'a>(&'a EventHandler); struct ClearOnDrop<'a>(&'a EventHandler);
@@ -69,10 +71,10 @@ impl EventHandler {
match self.0.inner.try_borrow_mut().as_deref_mut() { match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => { Ok(data @ Some(_)) => {
*data = None; *data = None;
} },
Ok(None) => { Ok(None) => {
tracing::error!("tried to clear handler, but no handler was set"); tracing::error!("tried to clear handler, but no handler was set");
} },
Err(_) => { Err(_) => {
// Note: This is not expected to ever happen, this // Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and // module generally controls the `RefCell`, and
@@ -83,7 +85,7 @@ impl EventHandler {
// weren't able to unset the handler. // weren't able to unset the handler.
eprintln!("tried to clear handler that is currently in use"); eprintln!("tried to clear handler that is currently in use");
std::process::abort(); std::process::abort();
} },
} }
} }
} }
@@ -102,35 +104,31 @@ impl EventHandler {
self.inner.try_borrow().is_err() self.inner.try_borrow().is_err()
} }
pub(crate) fn ready(&self) -> bool { pub(crate) fn handle(
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle_event(
&self, &self,
event: Event<HandlePendingUserEvents>, callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
event_loop: &RootActiveEventLoop, event_loop: &RootActiveEventLoop,
) { ) {
match self.inner.try_borrow_mut().as_deref_mut() { match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(EventHandlerData { handler })) => { Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here, // It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is // so that `in_use` can properly detect that the handler is
// still in use. // still in use.
// //
// If the handler unwinds, the `RefMut` will ensure that the // If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed. // handler is no longer borrowed.
(handler)(event, event_loop); callback(*user_app, event_loop);
} },
Ok(None) => { Ok(None) => {
// `NSApplication`, our app delegate and this handler are all // `NSApplication`, our app delegate and this handler are all
// global state and so it's not impossible that we could get // global state and so it's not impossible that we could get
// an event after the application has exited the `EventLoop`. // an event after the application has exited the `EventLoop`.
tracing::error!("tried to run event handler, but no handler was set"); tracing::error!("tried to run event handler, but no handler was set");
} },
Err(_) => { Err(_) => {
// Prevent re-entrancy. // Prevent re-entrancy.
panic!("tried to handle event while another event is currently being handled"); panic!("tried to handle event while another event is currently being handled");
} },
} }
} }
} }

View File

@@ -1,47 +1,38 @@
use std::{ use std::any::Any;
any::Any, use std::cell::Cell;
cell::Cell, use std::collections::VecDeque;
collections::VecDeque, use std::marker::PhantomData;
marker::PhantomData, use std::os::raw::c_void;
os::raw::c_void, use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}, use std::ptr;
ptr, use std::rc::{Rc, Weak};
rc::{Rc, Weak}, use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
sync::mpsc, use std::sync::Arc;
time::{Duration, Instant}, use std::time::{Duration, Instant};
};
use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, kCFRunLoopDefaultMode, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use icrate::AppKit::{ use objc2::rc::{autoreleasepool, Retained};
NSApplication, NSApplicationActivationPolicyAccessory, NSApplicationActivationPolicyProhibited, use objc2::runtime::ProtocolObject;
NSApplicationActivationPolicyRegular, NSWindow,
};
use icrate::Foundation::{MainThreadMarker, NSObjectProtocol};
use objc2::{msg_send_id, ClassType}; use objc2::{msg_send_id, ClassType};
use objc2::{ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
rc::{autoreleasepool, Id}, use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
runtime::ProtocolObject,
};
use super::app::WinitApplication;
use super::app_state::ApplicationDelegate;
use super::cursor::CustomCursor;
use super::event::dummy_event; use super::event::dummy_event;
use super::{ use super::monitor::{self, MonitorHandle};
app::WinitApplication, use super::observer::setup_control_flow_observers;
app_delegate::{ApplicationDelegate, HandlePendingUserEvents}, use crate::application::ApplicationHandler;
monitor::{self, MonitorHandle}, use crate::error::EventLoopError;
observer::setup_control_flow_observers, use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents};
}; use crate::platform::macos::ActivationPolicy;
use crate::platform_impl::platform::cursor::CustomCursor; use crate::platform::pump_events::PumpStatus;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
use crate::{
error::EventLoopError,
event::Event,
event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents, EventLoopClosed},
platform::{macos::ActivationPolicy, pump_events::PumpStatus},
};
#[derive(Default)] #[derive(Default)]
pub struct PanicInfo { pub struct PanicInfo {
@@ -60,12 +51,14 @@ impl PanicInfo {
self.inner.set(inner); self.inner.set(inner);
result result
} }
/// Overwrites the curret state if the current state is not panicking
/// Overwrites the current state if the current state is not panicking
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) { pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
if !self.is_panicking() { if !self.is_panicking() {
self.inner.set(Some(p)); self.inner.set(Some(p));
} }
} }
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> { pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
self.inner.take() self.inner.take()
} }
@@ -73,24 +66,23 @@ impl PanicInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
delegate: Id<ApplicationDelegate>, delegate: Retained<ApplicationDelegate>,
pub(super) mtm: MainThreadMarker, pub(super) mtm: MainThreadMarker,
} }
impl ActiveEventLoop { impl ActiveEventLoop {
pub(super) fn new_root(delegate: Id<ApplicationDelegate>) -> RootWindowTarget { pub(super) fn new_root(delegate: Retained<ApplicationDelegate>) -> RootWindowTarget {
let mtm = MainThreadMarker::from(&*delegate); let mtm = MainThreadMarker::from(&*delegate);
let p = Self { delegate, mtm }; let p = Self { delegate, mtm };
RootWindowTarget { RootWindowTarget { p, _marker: PhantomData }
p, }
_marker: PhantomData,
} pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
&self.delegate
} }
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor { pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor {
RootCustomCursor { RootCustomCursor { inner: CustomCursor::new(source.inner) }
inner: CustomCursor::new(source.inner),
}
} }
#[inline] #[inline]
@@ -118,9 +110,7 @@ impl ActiveEventLoop {
pub fn raw_display_handle_rwh_06( pub fn raw_display_handle_rwh_06(
&self, &self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit( Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
rwh_06::AppKitDisplayHandle::new(),
))
} }
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -146,9 +136,7 @@ impl ActiveEventLoop {
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle { pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
OwnedDisplayHandle OwnedDisplayHandle
} }
}
impl ActiveEventLoop {
pub(crate) fn hide_application(&self) { pub(crate) fn hide_application(&self) {
NSApplication::sharedApplication(self.mtm).hide(None) NSApplication::sharedApplication(self.mtm).hide(None)
} }
@@ -166,35 +154,19 @@ impl ActiveEventLoop {
} }
} }
fn map_user_event<T: 'static>( pub struct EventLoop {
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
receiver: Rc<mpsc::Receiver<T>>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
}
}
}
pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience. /// Store a reference to the application for convenience.
/// ///
/// We intentionally don't store `WinitApplication` since we want to have /// We intentionally don't store `WinitApplication` since we want to have
/// the possibility of swapping that out at some point. /// the possibility of swapping that out at some point.
app: Id<NSApplication>, app: Retained<NSApplication>,
/// The application delegate that we've registered. /// The application delegate that we've registered.
/// ///
/// The delegate is only weakly referenced by NSApplication, so we must /// The delegate is only weakly referenced by NSApplication, so we must
/// keep it around here as well. /// keep it around here as well.
delegate: Id<ApplicationDelegate>, delegate: Retained<ApplicationDelegate>,
// Event sender and receiver, used for EventLoopProxy. proxy_wake_up: Arc<AtomicBool>,
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: RootWindowTarget, window_target: RootWindowTarget,
panic_info: Rc<PanicInfo>, panic_info: Rc<PanicInfo>,
@@ -217,28 +189,35 @@ impl Default for PlatformSpecificEventLoopAttributes {
} }
} }
impl<T> EventLoop<T> { impl EventLoop {
pub(crate) fn new( pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes, attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> { ) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!"); .expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Id<NSApplication> = let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() { if !app.is_kind_of::<WinitApplication>() {
panic!("`winit` requires control over the principal class. You must create the event loop before other parts of your application initialize NSApplication"); panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
} }
let activation_policy = match attributes.activation_policy { let activation_policy = match attributes.activation_policy {
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
}; };
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let delegate = ApplicationDelegate::new( let delegate = ApplicationDelegate::new(
mtm, mtm,
activation_policy, activation_policy,
proxy_wake_up.clone(),
attributes.default_menu, attributes.default_menu,
attributes.activate_ignoring_other_apps, attributes.activate_ignoring_other_apps,
); );
@@ -248,18 +227,16 @@ impl<T> EventLoop<T> {
}); });
let panic_info: Rc<PanicInfo> = Default::default(); let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(Rc::downgrade(&panic_info)); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop { Ok(EventLoop {
app, app,
delegate: delegate.clone(), delegate: delegate.clone(),
sender,
receiver: Rc::new(receiver),
window_target: RootWindowTarget { window_target: RootWindowTarget {
p: ActiveEventLoop { delegate, mtm }, p: ActiveEventLoop { delegate, mtm },
_marker: PhantomData, _marker: PhantomData,
}, },
proxy_wake_up,
panic_info, panic_info,
}) })
} }
@@ -268,24 +245,19 @@ impl<T> EventLoop<T> {
&self.window_target &self.window_target
} }
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError> pub fn run_app<A: ApplicationHandler>(mut self, app: &mut A) -> Result<(), EventLoopError> {
where self.run_app_on_demand(app)
F: FnMut(Event<T>, &RootWindowTarget),
{
self.run_on_demand(handler)
} }
// NB: we don't base this on `pump_events` because for `MacOs` we can't support // NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of // `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to // time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups. // redundant wake ups.
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError> pub fn run_app_on_demand<A: ApplicationHandler>(
where &mut self,
F: FnMut(Event<T>, &RootWindowTarget), app: &mut A,
{ ) -> Result<(), EventLoopError> {
let handler = map_user_event(handler, self.receiver.clone()); self.delegate.set_event_handler(app, || {
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// clear / normalize pump_events state // clear / normalize pump_events state
self.delegate.set_wait_timeout(None); self.delegate.set_wait_timeout(None);
@@ -318,16 +290,15 @@ impl<T> EventLoop<T> {
Ok(()) Ok(())
} }
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus pub fn pump_app_events<A: ApplicationHandler>(
where &mut self,
F: FnMut(Event<T>, &RootWindowTarget), timeout: Option<Duration>,
{ app: &mut A,
let handler = map_user_event(handler, self.receiver.clone()); ) -> PumpStatus {
self.delegate.set_event_handler(app, || {
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least run // As a special case, if the application hasn't been launched yet then we at least
// the loop until it has fully launched. // run the loop until it has fully launched.
if !self.delegate.is_launched() { if !self.delegate.is_launched() {
debug_assert!(!self.delegate.is_running()); debug_assert!(!self.delegate.is_running());
@@ -335,31 +306,34 @@ impl<T> EventLoop<T> {
// SAFETY: We do not run the application re-entrantly // SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() }; unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application has launched // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched
} else if !self.delegate.is_running() { } else if !self.delegate.is_running() {
// Even though the application may have been launched, it's possible we aren't running // Even though the application may have been launched, it's possible we aren't
// if the `EventLoop` was run before and has since exited. This indicates that // running if the `EventLoop` was run before and has since
// we just starting to re-run the same `EventLoop` again. // exited. This indicates that we just starting to re-run
// the same `EventLoop` again.
self.delegate.set_is_running(true); self.delegate.set_is_running(true);
self.delegate.dispatch_init_events(); self.delegate.dispatch_init_events();
} else { } else {
// Only run for as long as the given `Duration` allows so we don't block the external loop. // Only run for as long as the given `Duration` allows so we don't block the
// external loop.
match timeout { match timeout {
Some(Duration::ZERO) => { Some(Duration::ZERO) => {
self.delegate.set_wait_timeout(None); self.delegate.set_wait_timeout(None);
self.delegate.set_stop_before_wait(true); self.delegate.set_stop_before_wait(true);
} },
Some(duration) => { Some(duration) => {
self.delegate.set_stop_before_wait(false); self.delegate.set_stop_before_wait(false);
let timeout = Instant::now() + duration; let timeout = Instant::now() + duration;
self.delegate.set_wait_timeout(Some(timeout)); self.delegate.set_wait_timeout(Some(timeout));
self.delegate.set_stop_after_wait(true); self.delegate.set_stop_after_wait(true);
} },
None => { None => {
self.delegate.set_wait_timeout(None); self.delegate.set_wait_timeout(None);
self.delegate.set_stop_before_wait(false); self.delegate.set_stop_before_wait(false);
self.delegate.set_stop_after_wait(true); self.delegate.set_stop_after_wait(true);
} },
} }
self.delegate.set_stop_on_redraw(true); self.delegate.set_stop_on_redraw(true);
// SAFETY: We do not run the application re-entrantly // SAFETY: We do not run the application re-entrantly
@@ -385,8 +359,8 @@ impl<T> EventLoop<T> {
}) })
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.proxy_wake_up.clone())
} }
} }
@@ -440,19 +414,19 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
let app = NSApplication::sharedApplication(mtm); let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app); stop_app_immediately(&app);
None None
} },
} }
} }
pub struct EventLoopProxy<T> { pub struct EventLoopProxy {
sender: mpsc::Sender<T>, proxy_wake_up: Arc<AtomicBool>,
source: CFRunLoopSourceRef, source: CFRunLoopSourceRef,
} }
unsafe impl<T: Send> Send for EventLoopProxy<T> {} unsafe impl Send for EventLoopProxy {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {} unsafe impl Sync for EventLoopProxy {}
impl<T> Drop for EventLoopProxy<T> { impl Drop for EventLoopProxy {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
CFRelease(self.source as _); CFRelease(self.source as _);
@@ -460,14 +434,14 @@ impl<T> Drop for EventLoopProxy<T> {
} }
} }
impl<T> Clone for EventLoopProxy<T> { impl Clone for EventLoopProxy {
fn clone(&self) -> Self { fn clone(&self) -> Self {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.proxy_wake_up.clone())
} }
} }
impl<T> EventLoopProxy<T> { impl EventLoopProxy {
fn new(sender: mpsc::Sender<T>) -> Self { fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
unsafe { unsafe {
// just wake up the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {} extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -487,25 +461,21 @@ impl<T> EventLoopProxy<T> {
cancel: None, cancel: None,
perform: event_loop_proxy_handler, perform: event_loop_proxy_handler,
}; };
let source = let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(rl);
EventLoopProxy { sender, source } EventLoopProxy { proxy_wake_up, source }
} }
} }
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> { pub fn wake_up(&self) {
self.sender self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
.send(event)
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe { unsafe {
// let the main thread know there's a new event // let the main thread know there's a new event
CFRunLoopSourceSignal(self.source); CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(rl);
} }
Ok(())
} }
} }

View File

@@ -4,14 +4,14 @@
use std::ffi::c_void; use std::ffi::c_void;
use core_foundation::{ use core_foundation::array::CFArrayRef;
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, use core_foundation::dictionary::CFDictionaryRef;
}; use core_foundation::string::CFStringRef;
use core_graphics::{ use core_foundation::uuid::CFUUIDRef;
base::CGError, use core_graphics::base::CGError;
display::{CGDirectDisplayID, CGDisplayConfigRef}, use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
}; use objc2::ffi::NSInteger;
use objc2::{ffi::NSInteger, runtime::AnyObject}; use objc2::runtime::AnyObject;
pub type CGDisplayFadeInterval = f32; pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32; pub type CGDisplayReservationInterval = f32;

View File

@@ -1,11 +1,8 @@
use icrate::AppKit::{ use objc2::rc::Retained;
NSApplication, NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlags,
NSMenu, NSMenuItem,
};
use icrate::Foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
use objc2::rc::Id;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::sel; use objc2::sel;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a NSString, key: &'a NSString,
@@ -23,12 +20,8 @@ pub fn initialize(app: &NSApplication) {
// About menu item // About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name); let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
let about_item = menu_item( let about_item =
mtm, menu_item(mtm, &about_item_title, Some(sel!(orderFrontStandardAboutPanel:)), None);
&about_item_title,
Some(sel!(orderFrontStandardAboutPanel:)),
None,
);
// Services menu item // Services menu item
let services_menu = NSMenu::new(mtm); let services_menu = NSMenu::new(mtm);
@@ -44,10 +37,7 @@ pub fn initialize(app: &NSApplication) {
mtm, mtm,
&hide_item_title, &hide_item_title,
Some(sel!(hide:)), Some(sel!(hide:)),
Some(KeyEquivalent { Some(KeyEquivalent { key: ns_string!("h"), masks: None }),
key: ns_string!("h"),
masks: None,
}),
); );
// Hide other applications menu item // Hide other applications menu item
@@ -58,18 +48,17 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)), Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent { Some(KeyEquivalent {
key: ns_string!("h"), key: ns_string!("h"),
masks: Some(NSEventModifierFlagOption | NSEventModifierFlagCommand), masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}), }),
); );
// Show applications menu item // Show applications menu item
let show_all_item_title = ns_string!("Show All"); let show_all_item_title = ns_string!("Show All");
let show_all_item = menu_item( let show_all_item =
mtm, menu_item(mtm, show_all_item_title, Some(sel!(unhideAllApplications:)), None);
show_all_item_title,
Some(sel!(unhideAllApplications:)),
None,
);
// Separator menu item // Separator menu item
let sep = NSMenuItem::separatorItem(mtm); let sep = NSMenuItem::separatorItem(mtm);
@@ -80,10 +69,7 @@ pub fn initialize(app: &NSApplication) {
mtm, mtm,
&quit_item_title, &quit_item_title,
Some(sel!(terminate:)), Some(sel!(terminate:)),
Some(KeyEquivalent { Some(KeyEquivalent { key: ns_string!("q"), masks: None }),
key: ns_string!("q"),
masks: None,
}),
); );
app_menu.addItem(&about_item); app_menu.addItem(&about_item);
@@ -105,7 +91,7 @@ fn menu_item(
title: &NSString, title: &NSString,
selector: Option<Sel>, selector: Option<Sel>,
key_equivalent: Option<KeyEquivalent<'_>>, key_equivalent: Option<KeyEquivalent<'_>>,
) -> Id<NSMenuItem> { ) -> Retained<NSMenuItem> {
let (key, masks) = match key_equivalent { let (key, masks) = match key_equivalent {
Some(ke) => (ke.key, ke.masks), Some(ke) => (ke.key, ke.masks),
None => (ns_string!(""), None), None => (ns_string!(""), None),

View File

@@ -2,7 +2,7 @@
mod util; mod util;
mod app; mod app;
mod app_delegate; mod app_state;
mod cursor; mod cursor;
mod event; mod event;
mod event_handler; mod event_handler;
@@ -17,16 +17,14 @@ mod window_delegate;
use std::fmt; use std::fmt;
pub(crate) use self::{ pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra}, pub(crate) use self::event_loop::{
event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes,
PlatformSpecificEventLoopAttributes,
},
monitor::{MonitorHandle, VideoModeHandle},
window::WindowId,
window_delegate::PlatformSpecificWindowAttributes,
}; };
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::WindowId;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
use crate::event::DeviceId as RootDeviceId; use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor; pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;

View File

@@ -1,18 +1,18 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::{collections::VecDeque, fmt}; use std::collections::VecDeque;
use std::fmt;
use core_foundation::{ use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
array::{CFArrayGetCount, CFArrayGetValueAtIndex}, use core_foundation::base::{CFRelease, TCFType};
base::{CFRelease, TCFType}, use core_foundation::string::CFString;
string::CFString,
};
use core_graphics::display::{ use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
}; };
use icrate::AppKit::NSScreen; use objc2::rc::Retained;
use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber, NSPoint, NSRect}; use objc2::runtime::AnyObject;
use objc2::{rc::Id, runtime::AnyObject}; use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
use super::ffi; use super::ffi;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
@@ -203,7 +203,7 @@ impl MonitorHandle {
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
MainThreadMarker::run_on_main(|mtm| { run_on_main(|mtm| {
match self.ns_screen(mtm) { match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64, Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen None => 1.0, // default to 1.0 when we can't find the screen
@@ -233,9 +233,7 @@ impl MonitorHandle {
return None; return None;
} }
(time.time_scale as i64) (time.time_scale as i64).checked_div(time.time_value).map(|v| (v * 1000) as u32)
.checked_div(time.time_value)
.map(|v| (v * 1000) as u32)
} }
} }
@@ -297,7 +295,7 @@ impl MonitorHandle {
} }
} }
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Id<NSScreen>> { pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) }; let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
NSScreen::screens(mtm).into_iter().find(|screen| { NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen); let other_native_id = get_display_id(screen);

View File

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

View File

@@ -0,0 +1,28 @@
use tracing::trace;
macro_rules! trace_scope {
($s:literal) => {
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
};
}
pub(crate) struct TraceGuard {
module_path: &'static str,
called_from_fn: &'static str,
}
impl TraceGuard {
#[inline]
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
trace!(target = module_path, "Triggered `{}`", called_from_fn);
Self { module_path, called_from_fn }
}
}
impl Drop for TraceGuard {
#[inline]
fn drop(&mut self) {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
}
}

View File

@@ -3,52 +3,45 @@ use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::ptr; use std::ptr;
use icrate::AppKit::{ use objc2::rc::{Retained, WeakId};
NSApplication, NSCursor, NSEvent, NSEventPhaseBegan, NSEventPhaseCancelled,
NSEventPhaseChanged, NSEventPhaseEnded, NSEventPhaseMayBegin, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView,
};
use icrate::Foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
NSString, NSUInteger,
};
use objc2::rc::{Id, WeakId};
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{ use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
}; };
use super::app_delegate::ApplicationDelegate; use super::app_state::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor}; use super::cursor::{default_cursor, invisible_cursor};
use super::event::{ use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, scancode_to_physicalkey,
}; };
use super::window::WinitWindow; use super::window::WinitWindow;
use super::{util, DEVICE_ID}; use super::DEVICE_ID;
use crate::{ use crate::dpi::{LogicalPosition, LogicalSize};
dpi::{LogicalPosition, LogicalSize}, use crate::event::{
event::{ DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
WindowEvent,
},
keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey},
platform::macos::OptionAsAlt,
}; };
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
use crate::window::WindowId as RootWindowId;
#[derive(Debug)] #[derive(Debug)]
struct CursorState { struct CursorState {
visible: bool, visible: bool,
cursor: Id<NSCursor>, cursor: Retained<NSCursor>,
} }
impl Default for CursorState { impl Default for CursorState {
fn default() -> Self { fn default() -> Self {
Self { Self { visible: true, cursor: default_cursor() }
visible: true,
cursor: default_cursor(),
}
} }
} }
@@ -116,8 +109,11 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
} }
} }
#[derive(Debug, Default)] #[derive(Debug)]
pub struct ViewState { pub struct ViewState {
/// Strong reference to the global application state.
app_delegate: Retained<ApplicationDelegate>,
cursor_state: RefCell<CursorState>, cursor_state: RefCell<CursorState>,
ime_position: Cell<NSPoint>, ime_position: Cell<NSPoint>,
ime_size: Cell<NSSize>, ime_size: Cell<NSSize>,
@@ -136,7 +132,7 @@ pub struct ViewState {
/// to the application, even during IME /// to the application, even during IME
forward_key_to_app: Cell<bool>, forward_key_to_app: Cell<bool>,
marked_text: RefCell<Id<NSMutableAttributedString>>, marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool, accepts_first_mouse: bool,
// Weak reference because the window keeps a strong reference to the view // Weak reference because the window keeps a strong reference to the view
@@ -175,7 +171,9 @@ declare_class!(
} }
let rect = self.frame(); let rect = self.frame();
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) }; let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect"); assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect)); self.ivars().tracking_rect.set(Some(tracking_rect));
} }
@@ -188,7 +186,9 @@ declare_class!(
} }
let rect = self.frame(); let rect = self.frame();
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) }; let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect"); assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect)); self.ivars().tracking_rect.set(Some(tracking_rect));
@@ -201,19 +201,15 @@ declare_class!(
} }
#[method(drawRect:)] #[method(drawRect:)]
fn draw_rect(&self, rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:"); trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
if let Some(window) = self.ivars()._ns_window.load() { if let Some(window) = self.ivars()._ns_window.load() {
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self)); self.ivars().app_delegate.handle_redraw(window.id());
app_delegate.handle_redraw(window.id());
} }
#[allow(clippy::let_unit_value)] // This is a direct subclass of NSView, no need to call superclass' drawRect:
unsafe {
let _: () = msg_send![super(self), drawRect: rect];
}
} }
#[method(acceptsFirstResponder)] #[method(acceptsFirstResponder)]
@@ -226,7 +222,7 @@ declare_class!(
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem // IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
// TODO: Add an API extension for using `NSTouchBar` // TODO: Add an API extension for using `NSTouchBar`
#[method_id(touchBar)] #[method_id(touchBar)]
fn touch_bar(&self) -> Option<Id<NSObject>> { fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar"); trace_scope!("touchBar");
None None
} }
@@ -259,33 +255,36 @@ declare_class!(
if length > 0 { if length > 0 {
NSRange::new(0, length) NSRange::new(0, length)
} else { } else {
util::EMPTY_RANGE // Documented to return `{NSNotFound, 0}` if there is no marked range.
NSRange::new(NSNotFound as NSUInteger, 0)
} }
} }
#[method(selectedRange)] #[method(selectedRange)]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange"); trace_scope!("selectedRange");
util::EMPTY_RANGE // Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0)
} }
#[method(setMarkedText:selectedRange:replacementRange:)] #[method(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text( fn set_marked_text(
&self, &self,
string: &NSObject, string: &NSObject,
_selected_range: NSRange, selected_range: NSRange,
_replacement_range: NSRange, _replacement_range: NSRange,
) { ) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:"); trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let (marked_text, preedit_string) = if string.is_kind_of::<NSAttributedString>() { let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string; let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast(); let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string }; let string = unsafe { &*string };
( (
NSMutableAttributedString::from_attributed_nsstring(string), NSMutableAttributedString::from_attributed_nsstring(string),
string.string().to_string(), string.string(),
) )
} else { } else {
let string: *const NSObject = string; let string: *const NSObject = string;
@@ -293,7 +292,7 @@ declare_class!(
let string = unsafe { &*string }; let string = unsafe { &*string };
( (
NSMutableAttributedString::from_nsstring(string), NSMutableAttributedString::from_nsstring(string),
string.to_string(), string.copy(),
) )
}; };
@@ -313,16 +312,21 @@ declare_class!(
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} }
// Empty string basically means that there's no preedit, so indicate that by sending let cursor_range = if string.is_empty() {
// `None` cursor range. // An empty string basically means that there's no preedit, so indicate that by
let cursor_range = if preedit_string.is_empty() { // sending a `None` cursor range.
None None
} else { } else {
Some((preedit_string.len(), preedit_string.len())) // Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
}; };
// Send WindowEvent for updating marked text // Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
} }
#[method(unmarkText)] #[method(unmarkText)]
@@ -343,7 +347,7 @@ declare_class!(
} }
#[method_id(validAttributesForMarkedText)] #[method_id(validAttributesForMarkedText)]
fn valid_attributes_for_marked_text(&self) -> Id<NSArray<NSAttributedStringKey>> { fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText"); trace_scope!("validAttributesForMarkedText");
NSArray::new() NSArray::new()
} }
@@ -353,7 +357,7 @@ declare_class!(
&self, &self,
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> Option<Id<NSAttributedString>> { ) -> Option<Retained<NSAttributedString>> {
trace_scope!("attributedSubstringForProposedRange:actualRange:"); trace_scope!("attributedSubstringForProposedRange:actualRange:");
None None
} }
@@ -371,13 +375,18 @@ declare_class!(
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:"); trace_scope!("firstRectForCharacterRange:actualRange:");
let rect = dbg!(NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get())); let rect = NSRect::new(
self.ivars().ime_position.get(),
self.ivars().ime_size.get()
);
// Return value is expected to be in screen coordinates, so we need a conversion here // Return value is expected to be in screen coordinates, so we need a conversion here
unsafe { self.window().convertRectToScreen(self.convertRect_toView(rect, None)) } self.window()
.convertRectToScreen(self.convertRect_toView(rect, None))
} }
#[method(insertText:replacementRange:)] #[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:"); trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
@@ -415,7 +424,8 @@ declare_class!(
self.ivars().forward_key_to_app.set(true); self.ivars().forward_key_to_app.set(true);
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit { if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit
{
// Leave preedit so that we also report the key-up for this key. // Leave preedit so that we also report the key-up for this key.
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} }
@@ -552,6 +562,17 @@ declare_class!(
}); });
} }
// In the past (?), `mouseMoved:` events were not generated when the
// user hovered over a window from a separate window, and as such the
// application might not know the location of the mouse in the event.
//
// To fix this, we emit `mouse_motion` inside of mouse click, mouse
// scroll, magnify and other gesture event handlers, to ensure that
// the application's state of where the mouse click was located is up
// to date.
//
// See https://github.com/rust-windowing/winit/pull/1490 for history.
#[method(mouseDown:)] #[method(mouseDown:)]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); trace_scope!("mouseDown:");
@@ -655,26 +676,20 @@ declare_class!(
// report the touch phase. // report the touch phase.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } { let phase = match unsafe { event.momentumPhase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => { NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
TouchPhase::Started NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
}
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => match unsafe { event.phase() } { _ => match unsafe { event.phase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => { NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
TouchPhase::Started NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
}
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => TouchPhase::Moved, _ => TouchPhase::Moved,
}, },
}; };
self.update_modifiers(event, false); self.update_modifiers(event, false);
self.queue_device_event(DeviceEvent::MouseWheel { delta }); self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel { self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID, device_id: DEVICE_ID,
delta, delta,
@@ -686,12 +701,14 @@ declare_class!(
fn magnify_with_event(&self, event: &NSEvent) { fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:"); trace_scope!("magnifyWithEvent:");
self.mouse_motion(event);
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } { let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started, NSEventPhase::Began => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved, NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled, NSEventPhase::Cancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended, NSEventPhase::Ended => TouchPhase::Ended,
_ => return, _ => return,
}; };
@@ -703,9 +720,11 @@ declare_class!(
} }
#[method(smartMagnifyWithEvent:)] #[method(smartMagnifyWithEvent:)]
fn smart_magnify_with_event(&self, _event: &NSEvent) { fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:"); trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture { self.queue_event(WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID, device_id: DEVICE_ID,
}); });
@@ -715,12 +734,14 @@ declare_class!(
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:"); trace_scope!("rotateWithEvent:");
self.mouse_motion(event);
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } { let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started, NSEventPhase::Began => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved, NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled, NSEventPhase::Cancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended, NSEventPhase::Ended => TouchPhase::Ended,
_ => return, _ => return,
}; };
@@ -735,8 +756,6 @@ declare_class!(
fn pressure_change_with_event(&self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:"); trace_scope!("pressureChangeWithEvent:");
self.mouse_motion(event);
self.queue_event(WindowEvent::TouchpadPressure { self.queue_event(WindowEvent::TouchpadPressure {
device_id: DEVICE_ID, device_id: DEVICE_ID,
pressure: unsafe { event.pressure() }, pressure: unsafe { event.pressure() },
@@ -763,34 +782,40 @@ declare_class!(
impl WinitView { impl WinitView {
pub(super) fn new( pub(super) fn new(
app_delegate: &ApplicationDelegate,
window: &WinitWindow, window: &WinitWindow,
accepts_first_mouse: bool, accepts_first_mouse: bool,
option_as_alt: OptionAsAlt, option_as_alt: OptionAsAlt,
) -> Id<Self> { ) -> Retained<Self> {
let mtm = MainThreadMarker::from(window); let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState { let this = mtm.alloc().set_ivars(ViewState {
app_delegate: app_delegate.retain(),
cursor_state: Default::default(),
ime_position: Default::default(),
ime_size: Default::default(),
modifiers: Default::default(),
phys_modifiers: Default::default(),
tracking_rect: Default::default(),
ime_state: Default::default(),
input_source: Default::default(),
ime_allowed: Default::default(),
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse, accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()), _ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt), option_as_alt: Cell::new(option_as_alt),
..Default::default()
}); });
let this: Id<Self> = unsafe { msg_send_id![super(this), init] }; let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true); this.setPostsFrameChangedNotifications(true);
let notification_center: &AnyObject = let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
// About frame change
let frame_did_change_notification_name =
NSString::from_str("NSViewFrameDidChangeNotification");
#[allow(clippy::let_unit_value)]
unsafe { unsafe {
let _: () = msg_send![ notification_center.addObserver_selector_name_object(
notification_center, &this,
addObserver: &*this, sel!(frameDidChange:),
selector: sel!(frameDidChange:), Some(NSViewFrameDidChangeNotification),
name: &*frame_did_change_notification_name, Some(&this),
object: &*this, )
];
} }
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
@@ -798,26 +823,20 @@ impl WinitView {
this this
} }
fn window(&self) -> Id<WinitWindow> { fn window(&self) -> Retained<WinitWindow> {
// TODO: Simply use `window` property on `NSView`. // TODO: Simply use `window` property on `NSView`.
// That only returns a window _after_ the view has been attached though! // That only returns a window _after_ the view has been attached though!
// (which is incompatible with `frameDidChange:`) // (which is incompatible with `frameDidChange:`)
// //
// unsafe { msg_send_id![self, window] } // unsafe { msg_send_id![self, window] }
self.ivars() self.ivars()._ns_window.load().expect("view to have a window")
._ns_window
.load()
.expect("view to have a window")
} }
fn queue_event(&self, event: WindowEvent) { fn queue_event(&self, event: WindowEvent) {
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self)); let window_id = RootWindowId(self.window().id());
app_delegate.queue_window_event(self.window().id(), event); self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
} app.window_event(event_loop, window_id, event);
});
fn queue_device_event(&self, event: DeviceEvent) {
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
app_delegate.queue_device_event(event);
} }
fn scale_factor(&self) -> f64 { fn scale_factor(&self) -> f64 {
@@ -836,11 +855,11 @@ impl WinitView {
.unwrap_or_default() .unwrap_or_default()
} }
pub(super) fn cursor_icon(&self) -> Id<NSCursor> { pub(super) fn cursor_icon(&self) -> Retained<NSCursor> {
self.ivars().cursor_state.borrow().cursor.clone() self.ivars().cursor_state.borrow().cursor.clone()
} }
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) { pub(super) fn set_cursor_icon(&self, icon: Retained<NSCursor>) {
let mut cursor_state = self.ivars().cursor_state.borrow_mut(); let mut cursor_state = self.ivars().cursor_state.borrow_mut();
cursor_state.cursor = icon; cursor_state.cursor = icon;
} }
@@ -935,9 +954,7 @@ impl WinitView {
let location_mask = ModLocationMask::from_location(event.location); let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
.entry(key)
.or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier); let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2); let mut events = VecDeque::with_capacity(2);
@@ -1075,7 +1092,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// NOTE: to get option as alt working we need to rewrite events // NOTE: to get option as alt working we need to rewrite events
// we're getting from the operating system, which makes it // we're getting from the operating system, which makes it
// impossible to provide such events as extra in `KeyEvent`. // impossible to provide such events as extra in `KeyEvent`.
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> { fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> {
let ev_mods = event_mods(event).state; let ev_mods = event_mods(event).state;
let ignore_alt_characters = match option_as_alt { let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true, OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
@@ -1087,9 +1104,7 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
if ignore_alt_characters { if ignore_alt_characters {
let ns_chars = unsafe { let ns_chars = unsafe {
event event.charactersIgnoringModifiers().expect("expected characters to be non-null")
.charactersIgnoringModifiers()
.expect("expected characters to be non-null")
}; };
unsafe { unsafe {

View File

@@ -1,9 +1,9 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use icrate::AppKit::{NSResponder, NSWindow}; use objc2::rc::{autoreleasepool, Retained};
use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSObject};
use objc2::rc::{autoreleasepool, Id};
use objc2::{declare_class, mutability, ClassType, DeclaredClass}; 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::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate; use super::window_delegate::WindowDelegate;
@@ -11,15 +11,14 @@ use crate::error::OsError as RootOsError;
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
pub(crate) struct Window { pub(crate) struct Window {
window: MainThreadBound<Id<WinitWindow>>, window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here. /// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Id<WindowDelegate>>, delegate: MainThreadBound<Retained<WindowDelegate>>,
} }
impl Drop for Window { impl Drop for Window {
fn drop(&mut self) { fn drop(&mut self) {
self.window self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
.get_on_main(|window| autoreleasepool(|_| window.close()))
} }
} }
@@ -29,7 +28,9 @@ impl Window {
attributes: WindowAttributes, attributes: WindowAttributes,
) -> Result<Self, RootOsError> { ) -> Result<Self, RootOsError> {
let mtm = window_target.mtm; let mtm = window_target.mtm;
let delegate = autoreleasepool(|_| WindowDelegate::new(attributes, mtm))?; let delegate = autoreleasepool(|_| {
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
})?;
Ok(Window { Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm), window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm), delegate: MainThreadBound::new(delegate, mtm),
@@ -65,9 +66,7 @@ impl Window {
pub(crate) fn raw_display_handle_rwh_06( pub(crate) fn raw_display_handle_rwh_06(
&self, &self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit( Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
rwh_06::AppKitDisplayHandle::new(),
))
} }
} }

View File

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

View File

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

View File

@@ -1,36 +1,32 @@
#![deny(unused_results)] #![deny(unused_results)]
use std::{ use std::cell::{RefCell, RefMut};
cell::{RefCell, RefMut}, use std::collections::HashSet;
collections::HashSet, use std::os::raw::c_void;
fmt, mem, use std::sync::{Arc, Mutex, OnceLock};
os::raw::c_void, use std::time::Instant;
ptr, use std::{fmt, mem, ptr};
sync::{Arc, Mutex, OnceLock},
time::Instant,
};
use core_foundation::base::CFRelease; use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, kCFRunLoopDefaultMode, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
}; };
use icrate::Foundation::{ use objc2::rc::Retained;
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
};
use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{msg_send, sel}; use objc2::{msg_send, sel};
use objc2_foundation::{
use super::uikit::UIView; CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
use super::window::WinitUIWindow; NSProcessInfo,
use crate::{
dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow},
window::WindowId as RootWindowId,
}; };
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 { macro_rules! bug {
($($msg:tt)*) => { ($($msg:tt)*) => {
@@ -44,12 +40,9 @@ macro_rules! bug_assert {
}; };
} }
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler { pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>, pub(crate) handler: Box<dyn FnMut(Event, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop, pub(crate) event_loop: RootActiveEventLoop,
} }
@@ -63,20 +56,20 @@ impl fmt::Debug for EventLoopHandler {
} }
impl EventLoopHandler { impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) { fn handle_event(&mut self, event: Event) {
(self.handler)(event, &self.event_loop) (self.handler)(event, &self.event_loop)
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum EventWrapper { pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>), StaticEvent(Event),
ScaleFactorChanged(ScaleFactorChanged), ScaleFactorChanged(ScaleFactorChanged),
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ScaleFactorChanged { pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>, pub(super) window: Retained<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>, pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64, pub(super) scale_factor: f64,
} }
@@ -92,15 +85,9 @@ enum UserCallbackTransitionResult<'a> {
}, },
} }
impl Event<HandlePendingUserEvents> { impl Event {
fn is_redraw(&self) -> bool { fn is_redraw(&self) -> bool {
matches!( matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
self,
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
}
)
} }
} }
@@ -109,25 +96,25 @@ impl Event<HandlePendingUserEvents> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl { enum AppStateImpl {
NotLaunched { NotLaunched {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
}, },
Launching { Launching {
queued_windows: Vec<Id<WinitUIWindow>>, queued_windows: Vec<Retained<WinitUIWindow>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_handler: EventLoopHandler, queued_handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
}, },
ProcessingEvents { ProcessingEvents {
handler: EventLoopHandler, handler: EventLoopHandler,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
// special state to deal with reentrancy and prevent mutable aliasing. // special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback { InUserCallback {
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>, queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
}, },
ProcessingRedraws { ProcessingRedraws {
handler: EventLoopHandler, handler: EventLoopHandler,
@@ -216,10 +203,7 @@ impl AppState {
} }
fn has_launched(&self) -> bool { fn has_launched(&self) -> bool {
!matches!( !matches!(self.state(), AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. })
self.state(),
AppStateImpl::NotLaunched { .. } | AppStateImpl::Launching { .. }
)
} }
fn has_terminated(&self) -> bool { fn has_terminated(&self) -> bool {
@@ -228,11 +212,9 @@ impl AppState {
fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) { fn will_launch_transition(&mut self, queued_handler: EventLoopHandler) {
let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::NotLaunched { AppStateImpl::NotLaunched { queued_windows, queued_events, queued_gpu_redraws } => {
queued_windows, (queued_windows, queued_events, queued_gpu_redraws)
queued_events, },
queued_gpu_redraws,
} => (queued_windows, queued_events, queued_gpu_redraws),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::Launching { self.set_state(AppStateImpl::Launching {
@@ -243,19 +225,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() { let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching { AppStateImpl::Launching {
queued_windows, queued_windows,
queued_events, queued_events,
queued_handler, queued_handler,
queued_gpu_redraws, 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), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingEvents { self.set_state(AppStateImpl::ProcessingEvents {
@@ -274,17 +253,10 @@ impl AppState {
} }
let (handler, event) = match (self.control_flow, self.take_state()) { let (handler, event) = match (self.control_flow, self.take_state()) {
(ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => ( (ControlFlow::Poll, AppStateImpl::PollFinished { waiting_handler }) => {
waiting_handler, (waiting_handler, EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)))
EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), },
), (ControlFlow::Wait, AppStateImpl::Waiting { waiting_handler, start }) => (
(
ControlFlow::Wait,
AppStateImpl::Waiting {
waiting_handler,
start,
},
) => (
waiting_handler, waiting_handler,
EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled {
start, start,
@@ -293,10 +265,7 @@ impl AppState {
), ),
( (
ControlFlow::WaitUntil(requested_resume), ControlFlow::WaitUntil(requested_resume),
AppStateImpl::Waiting { AppStateImpl::Waiting { waiting_handler, start },
waiting_handler,
start,
},
) => { ) => {
let event = if Instant::now() >= requested_resume { let event = if Instant::now() >= requested_resume {
EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached {
@@ -310,7 +279,7 @@ impl AppState {
})) }))
}; };
(waiting_handler, event) (waiting_handler, event)
} },
s => bug!("`EventHandler` unexpectedly woke up {:?}", s), s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
}; };
@@ -326,18 +295,9 @@ impl AppState {
// If we're not able to process an event due to recursion or `Init` not having been sent out // 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. // yet, then queue the events up.
match self.state_mut() { match self.state_mut() {
&mut AppStateImpl::Launching { &mut AppStateImpl::Launching { ref mut queued_events, .. }
ref mut queued_events, | &mut AppStateImpl::NotLaunched { ref mut queued_events, .. }
.. | &mut AppStateImpl::InUserCallback { 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 // A lifetime cast: early returns are not currently handled well with NLL, but
// polonius handles them well. This transmute is a safe workaround. // polonius handles them well. This transmute is a safe workaround.
return unsafe { return unsafe {
@@ -348,60 +308,49 @@ impl AppState {
queued_events, queued_events,
}) })
}; };
} },
&mut AppStateImpl::ProcessingEvents { .. } &mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {} | &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::PollFinished { .. } s @ &mut AppStateImpl::PollFinished { .. }
| s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::Terminated => { | s @ &mut AppStateImpl::Terminated => {
bug!("unexpected attempted to process an event {:?}", s) bug!("unexpected attempted to process an event {:?}", s)
} },
} }
let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = let (handler, queued_gpu_redraws, active_control_flow, processing_redraws) = match self
match self.take_state() { .take_state()
AppStateImpl::Launching { .. } {
| AppStateImpl::NotLaunched { .. } AppStateImpl::Launching { .. }
| AppStateImpl::InUserCallback { .. } => unreachable!(), | AppStateImpl::NotLaunched { .. }
AppStateImpl::ProcessingEvents { | AppStateImpl::InUserCallback { .. } => unreachable!(),
handler, AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
queued_gpu_redraws, (handler, queued_gpu_redraws, active_control_flow, false)
active_control_flow, },
} => (handler, queued_gpu_redraws, active_control_flow, false), AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
AppStateImpl::ProcessingRedraws { (handler, Default::default(), active_control_flow, true)
handler, },
active_control_flow, AppStateImpl::PollFinished { .. }
} => (handler, Default::default(), active_control_flow, true), | AppStateImpl::Waiting { .. }
AppStateImpl::PollFinished { .. } | AppStateImpl::Terminated => unreachable!(),
| AppStateImpl::Waiting { .. } };
| AppStateImpl::Terminated => unreachable!(),
};
self.set_state(AppStateImpl::InUserCallback { self.set_state(AppStateImpl::InUserCallback {
queued_events: Vec::new(), queued_events: Vec::new(),
queued_gpu_redraws, queued_gpu_redraws,
}); });
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws }
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() { let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
handler, (handler, queued_gpu_redraws, active_control_flow)
queued_gpu_redraws, },
active_control_flow,
} => (handler, queued_gpu_redraws, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
self.set_state(AppStateImpl::ProcessingRedraws { self.set_state(AppStateImpl::ProcessingRedraws { handler, active_control_flow });
handler,
active_control_flow,
});
queued_gpu_redraws queued_gpu_redraws
} }
@@ -410,10 +359,9 @@ impl AppState {
return; return;
} }
let (waiting_handler, old) = match self.take_state() { let (waiting_handler, old) = match self.take_state() {
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws { handler, active_control_flow } => {
handler, (handler, active_control_flow)
active_control_flow, },
} => (handler, active_control_flow),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
@@ -421,41 +369,29 @@ impl AppState {
match (old, new) { match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => { (ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting { waiting_handler, start });
waiting_handler, },
start,
});
}
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant => if old_instant == new_instant =>
{ {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting { waiting_handler, start });
waiting_handler, },
start,
});
}
(_, ControlFlow::Wait) => { (_, ControlFlow::Wait) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting { waiting_handler, start });
waiting_handler,
start,
});
self.waker.stop() self.waker.stop()
} },
(_, ControlFlow::WaitUntil(new_instant)) => { (_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now(); let start = Instant::now();
self.set_state(AppStateImpl::Waiting { self.set_state(AppStateImpl::Waiting { waiting_handler, start });
waiting_handler,
start,
});
self.waker.start_at(new_instant) self.waker.start_at(new_instant)
} },
// Unlike on macOS, handle Poll to Poll transition here to call the waker // Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => { (_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished { waiting_handler }); self.set_state(AppStateImpl::PollFinished { waiting_handler });
self.waker.start() self.waker.start()
} },
} }
} }
@@ -475,54 +411,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); let mut this = AppState::get_mut(mtm);
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
ref mut queued_windows, return queued_windows.push(window.clone())
.. },
} => return queued_windows.push(window.clone()),
&mut AppStateImpl::ProcessingEvents { .. } &mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. } | &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {} | &mut AppStateImpl::ProcessingRedraws { .. } => {},
s @ &mut AppStateImpl::Launching { .. } s @ &mut AppStateImpl::Launching { .. }
| s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => { &mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated") panic!("Attempt to create a `Window` after the app has terminated")
} },
} }
drop(this); drop(this);
window.makeKeyAndVisible(); 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); let mut this = AppState::get_mut(mtm);
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. }
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::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); let _ = queued_gpu_redraws.insert(window);
} },
s @ &mut AppStateImpl::ProcessingRedraws { .. } s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. } | s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => { &mut AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated") panic!("Attempt to create a `Window` after the app has terminated")
} },
} }
} }
@@ -566,10 +489,8 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)))
StartCause::Init, .chain(events);
)))
.chain(events);
handle_nonuser_events(mtm, events); handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created. // the above window dance hack, could possibly trigger new windows to be created.
@@ -609,7 +530,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
queued_events.extend(events); queued_events.extend(events);
return; return;
} },
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, handler,
active_control_flow, active_control_flow,
@@ -630,7 +551,7 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
); );
} }
handler.handle_event(event) handler.handle_event(event)
} },
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
@@ -638,18 +559,16 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
ref mut queued_events, mem::take(queued_events)
queued_gpu_redraws: _, },
} => mem::take(queued_events),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
if queued_events.is_empty() { if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() { let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_events: _, queued_gpu_redraws
queued_gpu_redraws, },
} => queued_gpu_redraws,
_ => unreachable!(), _ => unreachable!(),
}; };
this.app_state = Some(if processing_redraws { this.app_state = Some(if processing_redraws {
@@ -657,16 +576,9 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
queued_gpu_redraws.is_empty(), queued_gpu_redraws.is_empty(),
"redraw queued while processing redraws" "redraw queued while processing redraws"
); );
AppStateImpl::ProcessingRedraws { AppStateImpl::ProcessingRedraws { handler, active_control_flow }
handler,
active_control_flow,
}
} else { } else {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow }
handler,
queued_gpu_redraws,
active_control_flow,
}
}); });
break; break;
} }
@@ -679,12 +591,13 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
tracing::info!("processing `RedrawRequested` during the main event loop"); tracing::info!("processing `RedrawRequested` during the main event loop");
} else if processing_redraws && !event.is_redraw() { } else if processing_redraws && !event.is_redraw() {
tracing::warn!( tracing::warn!(
"processing non-`RedrawRequested` event after the main event loop: {:#?}", "processing non-`RedrawRequested` event after the main event loop: \
{:#?}",
event event
); );
} }
handler.handle_event(event) handler.handle_event(event)
} },
EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event), EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(&mut handler, event),
} }
} }
@@ -697,7 +610,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { .. } => { UserCallbackTransitionResult::ReentrancyPrevented { .. } => {
bug!("unexpected attempted to process an event") bug!("unexpected attempted to process an event")
} },
UserCallbackTransitionResult::Success { UserCallbackTransitionResult::Success {
handler, handler,
active_control_flow, active_control_flow,
@@ -709,23 +622,21 @@ fn handle_user_events(mtm: MainThreadMarker) {
} }
drop(this); drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); handler.handle_event(Event::UserWakeUp);
loop { loop {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => {
ref mut queued_events, mem::take(queued_events)
queued_gpu_redraws: _, },
} => mem::take(queued_events),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
}; };
if queued_events.is_empty() { if queued_events.is_empty() {
let queued_gpu_redraws = match this.take_state() { let queued_gpu_redraws = match this.take_state() {
AppStateImpl::InUserCallback { AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => {
queued_events: _, queued_gpu_redraws
queued_gpu_redraws, },
} => queued_gpu_redraws,
_ => unreachable!(), _ => unreachable!(),
}; };
this.app_state = Some(AppStateImpl::ProcessingEvents { this.app_state = Some(AppStateImpl::ProcessingEvents {
@@ -744,17 +655,39 @@ fn handle_user_events(mtm: MainThreadMarker) {
} }
} }
handler.handle_event(Event::UserEvent(HandlePendingUserEvents)); handler.handle_event(Event::UserWakeUp);
} }
} }
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) { pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm); let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() { if !this.has_launched() || this.has_terminated() {
return; return;
} }
match this.state_mut() { match this.state_mut() {
AppStateImpl::ProcessingEvents { .. } => {} AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"), _ => bug!("`ProcessingRedraws` happened unexpectedly"),
}; };
drop(this); drop(this);
@@ -782,7 +715,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition(); 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 this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition(); let mut handler = this.terminated_transition();
drop(this); drop(this);
@@ -791,11 +744,7 @@ pub fn terminated(mtm: MainThreadMarker) {
} }
fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) { fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged) {
let ScaleFactorChanged { let ScaleFactorChanged { suggested_size, scale_factor, window } = event;
suggested_size,
scale_factor,
window,
} = event;
let new_inner_size = Arc::new(Mutex::new(suggested_size)); let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
@@ -814,7 +763,7 @@ fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged)
view.setFrame(new_frame); 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_controller = window.rootViewController().unwrap();
let view = view_controller.view().unwrap(); let view = view_controller.view().unwrap();
let bounds = window.bounds(); let bounds = window.bounds();
@@ -846,25 +795,25 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching // future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate( let timer = CFRunLoopTimerCreate(
ptr::null_mut(), ptr::null_mut(),
std::f64::MAX, f64::MAX,
0.000_000_1, 0.000_000_1,
0, 0,
0, 0,
wakeup_main_loop, wakeup_main_loop,
ptr::null_mut(), ptr::null_mut(),
); );
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); CFRunLoopAddTimer(rl, timer, kCFRunLoopDefaultMode);
EventLoopWaker { timer } EventLoopWaker { timer }
} }
} }
fn stop(&mut self) { fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
} }
fn start(&mut self) { fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
} }
fn start_at(&mut self, instant: Instant) { fn start_at(&mut self, instant: Instant) {
@@ -950,28 +899,21 @@ fn meets_requirements(
} }
fn get_version() -> NSOperatingSystemVersion { fn get_version() -> NSOperatingSystemVersion {
unsafe { let process_info = NSProcessInfo::processInfo();
let process_info = NSProcessInfo::processInfo(); let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
let atleast_ios_8: bool = msg_send![ // Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
&process_info, // versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
respondsToSelector: sel!(operatingSystemVersion) // 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
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions. // (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
// 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 // The minimum required iOS version is likely to grow in the future.
// simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
// has been tested to not even run on macOS 10.15 - Xcode 8 might? process_info.operatingSystemVersion()
//
// 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 { pub fn os_capabilities() -> OSCapabilities {
// Cache the version lookup for efficiency // Cache the version lookup for efficiency
static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new(); static OS_CAPABILITIES: OnceLock<OSCapabilities> = OnceLock::new();
OS_CAPABILITIES OS_CAPABILITIES.get_or_init(|| OSCapabilities::from_os_version(get_version())).clone()
.get_or_init(|| OSCapabilities::from_os_version(get_version()))
.clone()
} }

View File

@@ -1,38 +1,32 @@
use std::{ use std::collections::VecDeque;
collections::VecDeque, use std::ffi::{c_char, c_int, c_void};
ffi::c_void, use std::marker::PhantomData;
marker::PhantomData, use std::ptr::{self, NonNull};
ptr, use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
sync::mpsc::{self, Receiver, Sender}, use std::sync::Arc;
};
use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{ use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use icrate::Foundation::{MainThreadMarker, NSString}; use objc2::rc::Retained;
use objc2::ClassType; use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
use crate::{ use super::app_state::EventLoopHandler;
error::EventLoopError, use crate::application::ApplicationHandler;
event::Event, use crate::error::EventLoopError;
event_loop::{ use crate::event::Event;
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed, use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
}, use crate::window::{CustomCursor, CustomCursorSource};
platform::ios::Idiom,
platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents},
window::{CustomCursor, CustomCursorSource},
};
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, monitor, MonitorHandle};
use super::{
app_state::AppState,
uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen},
};
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
@@ -42,9 +36,7 @@ pub struct ActiveEventLoop {
impl ActiveEventLoop { impl ActiveEventLoop {
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor { pub fn create_custom_cursor(&self, source: CustomCursorSource) -> CustomCursor {
let _ = source.inner; let _ = source.inner;
CustomCursor { CustomCursor { inner: super::PlatformCustomCursor }
inner: super::PlatformCustomCursor,
}
} }
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
@@ -52,7 +44,8 @@ impl ActiveEventLoop {
} }
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main(self.mtm))) #[allow(deprecated)]
Some(MonitorHandle::new(UIScreen::mainScreen(self.mtm)))
} }
#[inline] #[inline]
@@ -69,9 +62,7 @@ impl ActiveEventLoop {
pub fn raw_display_handle_rwh_06( pub fn raw_display_handle_rwh_06(
&self, &self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::UiKit( Ok(rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new()))
rwh_06::UiKitDisplayHandle::new(),
))
} }
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
@@ -116,34 +107,44 @@ impl OwnedDisplayHandle {
} }
} }
fn map_user_event<T: 'static>( fn map_user_event<A: ApplicationHandler>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop), app: &mut A,
receiver: mpsc::Receiver<T>, proxy_wake_up: Arc<AtomicBool>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) { ) -> impl FnMut(Event, &RootActiveEventLoop) + '_ {
move |event, window_target| match event.map_nonuser_event() { move |event, window_target| match event {
Ok(event) => (handler)(event, window_target), Event::NewEvents(cause) => app.new_events(window_target, cause),
Err(_) => { Event::WindowEvent { window_id, event } => {
for event in receiver.try_iter() { app.window_event(window_target, window_id, event)
(handler)(Event::UserEvent(event), window_target); },
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserWakeUp => {
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
app.proxy_wake_up(window_target);
} }
} },
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
} }
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop {
mtm: MainThreadMarker, mtm: MainThreadMarker,
sender: Sender<T>, proxy_wake_up: Arc<AtomicBool>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop, window_target: RootActiveEventLoop,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {} pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> { impl EventLoop {
pub(crate) fn new( pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes, _: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> { ) -> Result<EventLoop, EventLoopError> {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread"); .expect("On iOS, `EventLoop` must be created on the main thread");
@@ -151,63 +152,59 @@ impl<T: 'static> EventLoop<T> {
unsafe { unsafe {
assert!( assert!(
!SINGLETON_INIT, !SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. \ "Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
`EventLoopProxy` might be helpful"
); );
SINGLETON_INIT = true; SINGLETON_INIT = true;
} }
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain` // this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers(); setup_control_flow_observers();
let proxy_wake_up = Arc::new(AtomicBool::new(false));
Ok(EventLoop { Ok(EventLoop {
mtm, mtm,
sender, proxy_wake_up,
receiver, window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
window_target: RootActiveEventLoop {
p: ActiveEventLoop { mtm },
_marker: PhantomData,
},
}) })
} }
pub fn run<F>(self, handler: F) -> ! pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
where let application: Option<Retained<UIApplication>> =
F: FnMut(Event<T>, &RootActiveEventLoop), unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
{
let application = UIApplication::shared(self.mtm);
assert!( assert!(
application.is_none(), application.is_none(),
"\ "\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \
Note: `EventLoop::run_app` calls `UIApplicationMain` on iOS", `EventLoop::run_app` calls `UIApplicationMain` on iOS",
); );
let handler = map_user_event(handler, self.receiver); let handler = map_user_event(app, self.proxy_wake_up.clone());
let handler = unsafe { let handler = unsafe {
std::mem::transmute::< std::mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>, Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>, Box<dyn FnMut(Event, &RootActiveEventLoop)>,
>(Box::new(handler)) >(Box::new(handler))
}; };
let handler = EventLoopHandler { let handler = EventLoopHandler { handler, event_loop: self.window_target };
handler,
event_loop: self.window_target,
};
app_state::will_launch(self.mtm, handler); app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized // Ensure application delegate is initialized
let _ = AppDelegate::class(); 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 { unsafe {
UIApplicationMain( UIApplicationMain(
0, *_NSGetArgc(),
ptr::null(), NonNull::new(*_NSGetArgv()).unwrap(),
None, None,
Some(&NSString::from_str(AppDelegate::NAME)), Some(&NSString::from_str(AppDelegate::NAME)),
) )
@@ -215,8 +212,8 @@ impl<T: 'static> EventLoop<T> {
unreachable!() unreachable!()
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.proxy_wake_up.clone())
} }
pub fn window_target(&self) -> &RootActiveEventLoop { pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -224,35 +221,21 @@ impl<T: 'static> EventLoop<T> {
} }
} }
// EventLoopExtIOS pub struct EventLoopProxy {
impl<T: 'static> EventLoop<T> { proxy_wake_up: Arc<AtomicBool>,
pub fn idiom(&self) -> Idiom {
match UIDevice::current(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => Idiom::Unspecified,
}
}
}
pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef, source: CFRunLoopSourceRef,
} }
unsafe impl<T: Send> Send for EventLoopProxy<T> {} unsafe impl Send for EventLoopProxy {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {} unsafe impl Sync for EventLoopProxy {}
impl<T> Clone for EventLoopProxy<T> { impl Clone for EventLoopProxy {
fn clone(&self) -> EventLoopProxy<T> { fn clone(&self) -> EventLoopProxy {
EventLoopProxy::new(self.sender.clone()) EventLoopProxy::new(self.proxy_wake_up.clone())
} }
} }
impl<T> Drop for EventLoopProxy<T> { impl Drop for EventLoopProxy {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
CFRunLoopSourceInvalidate(self.source); CFRunLoopSourceInvalidate(self.source);
@@ -261,8 +244,8 @@ impl<T> Drop for EventLoopProxy<T> {
} }
} }
impl<T> EventLoopProxy<T> { impl EventLoopProxy {
fn new(sender: Sender<T>) -> EventLoopProxy<T> { fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
unsafe { unsafe {
// just wake up the eventloop // just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {} extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -282,32 +265,29 @@ impl<T> EventLoopProxy<T> {
cancel: None, cancel: None,
perform: event_loop_proxy_handler, perform: event_loop_proxy_handler,
}; };
let source = let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(rl);
EventLoopProxy { sender, source } EventLoopProxy { proxy_wake_up, source }
} }
} }
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> { pub fn wake_up(&self) {
self.sender self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe { unsafe {
// let the main thread know there's a new event // let the main thread know there's a new event
CFRunLoopSourceSignal(self.source); CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain(); let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl); CFRunLoopWakeUp(rl);
} }
Ok(())
} }
} }
fn setup_control_flow_observers() { fn setup_control_flow_observers() {
unsafe { 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( extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef, _: CFRunLoopObserverRef,
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
@@ -341,7 +321,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -356,7 +336,7 @@ fn setup_control_flow_observers() {
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => {} // may happen when running on macOS kCFRunLoopExit => {}, // may happen when running on macOS
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -367,7 +347,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(), ptr::null_mut(),
kCFRunLoopAfterWaiting, kCFRunLoopAfterWaiting,
1, // repeat = true 1, // repeat = true
CFIndex::min_value(), CFIndex::MIN,
control_flow_begin_handler, control_flow_begin_handler,
ptr::null_mut(), ptr::null_mut(),
); );
@@ -387,7 +367,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(), ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting, kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true 1, // repeat = true
CFIndex::max_value(), CFIndex::MAX,
control_flow_end_handler, control_flow_end_handler,
ptr::null_mut(), ptr::null_mut(),
); );

View File

@@ -1,11 +1,9 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)] #![allow(clippy::let_unit_value)]
mod app_delegate; mod app_delegate;
mod app_state; mod app_state;
mod event_loop; mod event_loop;
mod monitor; mod monitor;
mod uikit;
mod view; mod view;
mod view_controller; mod view_controller;
mod window; mod window;
@@ -14,16 +12,15 @@ use std::fmt;
use crate::event::DeviceId as RootDeviceId; use crate::event::DeviceId as RootDeviceId;
pub(crate) use self::{ pub(crate) use self::event_loop::{
event_loop::{ ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle,
ActiveEventLoop, EventLoop, EventLoopProxy, OwnedDisplayHandle, PlatformSpecificEventLoopAttributes,
PlatformSpecificEventLoopAttributes, };
}, pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
monitor::{MonitorHandle, VideoModeHandle}, pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
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::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use crate::platform_impl::Fullscreen;

View File

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

View File

@@ -1,31 +1,34 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet}; use objc2::rc::Retained;
use objc2::rc::Id; use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::runtime::AnyClass; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2::{ use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, use objc2_ui_kit::{
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
}; };
use super::app_state::{self, EventWrapper}; 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 super::window::WinitUIWindow;
use crate::{ use super::DEVICE_ID;
dpi::PhysicalPosition, use crate::dpi::PhysicalPosition;
event::{Event, Force, Touch, TouchPhase, WindowEvent}, use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
platform_impl::platform::DEVICE_ID, use crate::window::{WindowAttributes, WindowId as RootWindowId};
window::{WindowAttributes, WindowId as RootWindowId},
};
pub struct WinitViewState { pub struct WinitViewState {
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>, pinch_gesture_recognizer: RefCell<Option<Retained<UIPinchGestureRecognizer>>>,
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>, doubletap_gesture_recognizer: RefCell<Option<Retained<UITapGestureRecognizer>>>,
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>, 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!( declare_class!(
@@ -34,7 +37,7 @@ declare_class!(
unsafe impl ClassType for WinitView { unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
type Super = UIView; type Super = UIView;
type Mutability = mutability::InteriorMutable; type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitUIView"; const NAME: &'static str = "WinitUIView";
} }
@@ -167,12 +170,23 @@ declare_class!(
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) { fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let phase = match recognizer.state() { let (phase, delta) = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started, UIGestureRecognizerState::Began => {
UIGestureRecognizerState::Changed => TouchPhase::Moved, self.ivars().pinch_last_delta.set(recognizer.scale());
UIGestureRecognizerState::Ended => TouchPhase::Ended, (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 => { 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), state => panic!("unexpected recognizer state: {:?}", state),
}; };
@@ -181,7 +195,7 @@ declare_class!(
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::PinchGesture { event: WindowEvent::PinchGesture {
device_id: DEVICE_ID, device_id: DEVICE_ID,
delta: recognizer.velocity() as _, delta: delta as f64,
phase, phase,
}, },
}); });
@@ -211,23 +225,88 @@ declare_class!(
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) { fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let window = self.window().unwrap(); let window = self.window().unwrap();
let phase = match recognizer.state() { let (phase, delta) = match recognizer.state() {
UIGestureRecognizerState::Began => TouchPhase::Started, UIGestureRecognizerState::Began => {
UIGestureRecognizerState::Changed => TouchPhase::Moved, self.ivars().rotation_last_delta.set(0.0);
UIGestureRecognizerState::Ended => TouchPhase::Ended,
(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 => { 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), state => panic!("unexpected recognizer state: {:?}", state),
}; };
// Flip the velocity to match macOS. // Make delta negative to match macos, convert to degrees
let delta = -recognizer.velocity() as _;
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::RotationGesture { event: WindowEvent::RotationGesture {
device_id: DEVICE_ID, device_id: DEVICE_ID,
delta, delta: -delta.to_degrees() as _,
phase,
},
});
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, phase,
}, },
}); });
@@ -236,37 +315,34 @@ declare_class!(
app_state::handle_nonuser_event(mtm, gesture_event); app_state::handle_nonuser_event(mtm, gesture_event);
} }
} }
);
extern_methods!( unsafe impl NSObjectProtocol for WinitView {}
#[allow(non_snake_case)]
unsafe impl WinitView { unsafe impl UIGestureRecognizerDelegate for WinitView {
fn window(&self) -> Option<Id<WinitUIWindow>> { #[method(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]
unsafe { msg_send_id![self, window] } fn should_recognize_simultaneously(&self, _gesture_recognizer: &UIGestureRecognizer, _other_gesture_recognizer: &UIGestureRecognizer) -> bool {
true
} }
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
msg_send_id![self, traitCollection]
}
// TODO: Allow the user to customize this
#[method(layerClass)]
pub(crate) fn layerClass() -> &'static AnyClass;
} }
); );
impl WinitView { impl WinitView {
pub(crate) fn new( pub(crate) fn new(
_mtm: MainThreadMarker, mtm: MainThreadMarker,
window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
frame: CGRect, frame: CGRect,
) -> Id<Self> { ) -> Retained<Self> {
let this = Self::alloc().set_ivars(WinitViewState { let this = mtm.alloc().set_ivars(WinitViewState {
pinch_gesture_recognizer: RefCell::new(None), pinch_gesture_recognizer: RefCell::new(None),
doubletap_gesture_recognizer: RefCell::new(None), doubletap_gesture_recognizer: RefCell::new(None),
rotation_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); this.setMultipleTouchEnabled(true);
@@ -277,12 +353,23 @@ impl WinitView {
this 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) { pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize { if should_recognize {
if self.ivars().pinch_gesture_recognizer.borrow().is_none() { if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
let pinch: Id<UIPinchGestureRecognizer> = unsafe { let pinch = unsafe {
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)] UIPinchGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(pinchGesture:)),
)
}; };
pinch.setDelegate(Some(ProtocolObject::from_ref(self)));
self.addGestureRecognizer(&pinch); self.addGestureRecognizer(&pinch);
self.ivars().pinch_gesture_recognizer.replace(Some(pinch)); self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
} }
@@ -291,12 +378,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) { pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize { if should_recognize {
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() { if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
let tap: Id<UITapGestureRecognizer> = unsafe { let tap = unsafe {
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)] UITapGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(doubleTapGesture:)),
)
}; };
tap.setDelegate(Some(ProtocolObject::from_ref(self)));
tap.setNumberOfTapsRequired(2); tap.setNumberOfTapsRequired(2);
tap.setNumberOfTouchesRequired(1); tap.setNumberOfTouchesRequired(1);
self.addGestureRecognizer(&tap); self.addGestureRecognizer(&tap);
@@ -308,15 +428,19 @@ impl WinitView {
} }
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) { pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
let mtm = MainThreadMarker::from(self);
if should_recognize { if should_recognize {
if self.ivars().rotation_gesture_recognizer.borrow().is_none() { if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
let rotation: Id<UIRotationGestureRecognizer> = unsafe { let rotation = unsafe {
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)] UIRotationGestureRecognizer::initWithTarget_action(
mtm.alloc(),
Some(self),
Some(sel!(rotationGesture:)),
)
}; };
rotation.setDelegate(Some(ProtocolObject::from_ref(self)));
self.addGestureRecognizer(&rotation); self.addGestureRecognizer(&rotation);
self.ivars() self.ivars().rotation_gesture_recognizer.replace(Some(rotation));
.rotation_gesture_recognizer
.replace(Some(rotation));
} }
} else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() { } else if let Some(recognizer) = self.ivars().rotation_gesture_recognizer.take() {
self.removeGestureRecognizer(&recognizer); self.removeGestureRecognizer(&recognizer);
@@ -329,9 +453,9 @@ impl WinitView {
let os_supports_force = app_state::os_capabilities().force_touch; let os_supports_force = app_state::os_capabilities().force_touch;
for touch in touches { for touch in touches {
let logical_location = touch.locationInView(None); let logical_location = touch.locationInView(None);
let touch_type = touch.type_(); let touch_type = touch.r#type();
let force = if os_supports_force { let force = if os_supports_force {
let trait_collection = unsafe { self.traitCollection() }; let trait_collection = self.traitCollection();
let touch_capability = trait_collection.forceTouchCapability(); let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support. // Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available if touch_capability == UIForceTouchCapability::Available
@@ -364,7 +488,7 @@ impl WinitView {
// 2 is UITouchPhase::Stationary and is not expected here // 2 is UITouchPhase::Stationary and is not expected here
UITouchPhase::Ended => TouchPhase::Ended, UITouchPhase::Ended => TouchPhase::Ended,
UITouchPhase::Cancelled => TouchPhase::Cancelled, UITouchPhase::Cancelled => TouchPhase::Cancelled,
_ => panic!("unexpected touch phase: {:?}", phase as i32), _ => panic!("unexpected touch phase: {phase:?}"),
}; };
let physical_location = { let physical_location = {

View File

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

View File

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

View File

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

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